From c570d5f60e9c32afa69b3085f2c02ed3995c5701 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 14 Aug 2023 12:42:01 -0700 Subject: [PATCH 0001/1215] improvement: handle non-cast-in-type queries --- lib/calculation.ex | 7 ++++++- lib/expr.ex | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/calculation.ex b/lib/calculation.ex index 69150ff9..de34819a 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -59,7 +59,12 @@ defmodule AshPostgres.Calculation do type ) - expr = Ecto.Query.dynamic(type(^expr, ^type)) + expr = + if type do + Ecto.Query.dynamic(type(^expr, ^type)) + else + expr + end {calculation.load, calculation.name, expr} end) diff --git a/lib/expr.ex b/lib/expr.ex index 188de1e4..720ee416 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -964,7 +964,11 @@ defmodule AshPostgres.Expr do arg1 = maybe_uuid_to_binary(arg2, arg1, arg1) type = AshPostgres.Types.parameterized_type(arg2, constraints) - Ecto.Query.dynamic(type(^do_dynamic_expr(query, arg1, bindings, embedded?, type), ^type)) + if type do + Ecto.Query.dynamic(type(^do_dynamic_expr(query, arg1, bindings, embedded?, type), ^type)) + else + do_dynamic_expr(query, arg1, bindings, embedded?, type) + end end defp do_dynamic_expr( From bac2e01b54ce80736d08c0be5489386de37ffd9f Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Thu, 17 Aug 2023 10:49:18 +1000 Subject: [PATCH 0002/1215] test: add failing test to demonstrate potential bug (#164) 1) test complex calculation (AshPostgres.Test.ComplexCalculationsTest) test/complex_calculations_test.exs:5 ** (RuntimeError) Error while building reference: latest_documentation_status code: |> AshPostgres.Test.ComplexCalculations.Api.load!([ stacktrace: (ash_postgres 1.3.41) lib/expr.ex:846: AshPostgres.Expr.do_dynamic_expr/5 (ash_postgres 1.3.41) lib/expr.ex:109: AshPostgres.Expr.do_dynamic_expr/5 (ash_postgres 1.3.41) lib/expr.ex:356: AshPostgres.Expr.do_dynamic_expr/5 (ash_postgres 1.3.41) lib/expr.ex:968: anonymous fn/6 in AshPostgres.Expr.do_dynamic_expr/5 (ecto 3.10.3) lib/ecto/query/builder/dynamic.ex:76: Ecto.Query.Builder.Dynamic.expand/3 (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3 (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4 (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3 (stdlib 5.0.2) lists.erl:1707: :lists.mapfoldl_1/3 (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4 (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3 (stdlib 5.0.2) lists.erl:1707: :lists.mapfoldl_1/3 (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4 (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3 (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4 (stdlib 5.0.2) lists.erl:1706: :lists.mapfoldl_1/3 (elixir 1.15.4) lib/macro.ex:653: Macro.do_traverse/4 (ecto 3.10.3) lib/ecto/query/builder/dynamic.ex:59: Ecto.Query.Builder.Dynamic.partially_expand/6 (ecto 3.10.3) lib/ecto/query/builder/select.ex:235: Ecto.Query.Builder.Select.expand_nested/3 (ecto 3.10.3) lib/ecto/query/builder/select.ex:274: Ecto.Query.Builder.Select.expand_nested_pair/3 (elixir 1.15.4) lib/enum.ex:1825: anonymous fn/3 in Enum.map_reduce/3 (stdlib 5.0.2) maps.erl:416: :maps.fold_1/4 (elixir 1.15.4) lib/enum.ex:2522: Enum.map_reduce/3 (ecto 3.10.3) lib/ecto/query/builder/select.ex:257: Ecto.Query.Builder.Select.expand_nested/3 (ecto 3.10.3) lib/ecto/query/builder/select.ex:205: Ecto.Query.Builder.Select.select!/5 (elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3 (ash_postgres 1.3.41) lib/aggregate.ex:121: anonymous fn/6 in AshPostgres.Aggregate.add_aggregates/6 (elixir 1.15.4) lib/enum.ex:4830: Enumerable.List.reduce/3 (elixir 1.15.4) lib/enum.ex:2564: Enum.reduce_while/3 (ash_postgres 1.3.41) lib/aggregate.ex:53: AshPostgres.Aggregate.add_aggregates/6 (ash 2.13.3) lib/ash/query/aggregate.ex:570: anonymous fn/6 in Ash.Query.Aggregate.value_request/9 (ash 2.13.3) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1 (ash 2.13.3) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2 (elixir 1.15.4) lib/task/supervised.ex:101: Task.Supervised.invoke_mfa/2 (elixir 1.15.4) lib/task/supervised.ex:36: Task.Supervised.reply/4 (ash 2.13.3) lib/ash/engine/engine.ex:552: Ash.Engine.async/2 (elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2 (elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2 (ash 2.13.3) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1 (ash 2.13.3) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1 (ash 2.13.3) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2 (ash 2.13.3) lib/ash/engine/engine.ex:148: Ash.Engine.run/2 (ash 2.13.3) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3 (ash 2.13.3) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3 (ash 2.13.3) lib/ash/api/api.ex:1733: Ash.Api.load/4 (ash 2.13.3) lib/ash/api/api.ex:1707: Ash.Api.load!/4 test/complex_calculations_test.exs:55: (test) --- config/config.exs | 6 +- .../20230816231942.json | 29 ++++++ .../20230816231942.json | 95 +++++++++++++++++++ .../20230816231942.json | 65 +++++++++++++ ...6231942_add_complex_calculation_tables.exs | 69 ++++++++++++++ test/complex_calculations_test.exs | 60 ++++++++++++ test/support/complex_calculations/api.ex | 8 ++ test/support/complex_calculations/registry.ex | 10 ++ .../resources/certification.ex | 48 ++++++++++ .../resources/documentation.ex | 48 ++++++++++ .../complex_calculations/resources/skill.ex | 50 ++++++++++ 11 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json create mode 100644 priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs create mode 100644 test/complex_calculations_test.exs create mode 100644 test/support/complex_calculations/api.ex create mode 100644 test/support/complex_calculations/registry.ex create mode 100644 test/support/complex_calculations/resources/certification.ex create mode 100644 test/support/complex_calculations/resources/documentation.ex create mode 100644 test/support/complex_calculations/resources/skill.ex diff --git a/config/config.exs b/config/config.exs index 7b5b8db1..7e974dba 100644 --- a/config/config.exs +++ b/config/config.exs @@ -44,7 +44,11 @@ if Mix.env() == :test do config :ash_postgres, ecto_repos: [AshPostgres.TestRepo, AshPostgres.TestNoSandboxRepo], - ash_apis: [AshPostgres.Test.Api, AshPostgres.MultitenancyTest.Api] + ash_apis: [ + AshPostgres.Test.Api, + AshPostgres.MultitenancyTest.Api, + AshPostgres.Test.ComplexCalculations.Api + ] config :logger, level: :warning end diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json new file mode 100644 index 00000000..f406ca5c --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json @@ -0,0 +1,29 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + } + ], + "table": "complex_calculations_certifications", + "hash": "A4D8129693BDC95C72E91842B17BB1B44951D91A87DC166A3371D052E4D27C1F", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json new file mode 100644 index 00000000..b0e4ff19 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json @@ -0,0 +1,95 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "documented_at", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "skill_id", + "references": { + "name": "complex_calculations_documentations_skill_id_fkey", + "table": "complex_calculations_skills", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "destination_attribute": "id", + "on_delete": null, + "on_update": null, + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + } + ], + "table": "complex_calculations_documentations", + "hash": "26FCE27CAB6B3B67C7E099831352AF89AFD352CBD6E30043C4354B0400F09FBD", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json b/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json new file mode 100644 index 00000000..1e0c6343 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json @@ -0,0 +1,65 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "false", + "size": null, + "type": "boolean", + "source": "removed", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "certification_id", + "references": { + "name": "complex_calculations_skills_certification_id_fkey", + "table": "complex_calculations_certifications", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "destination_attribute": "id", + "on_delete": null, + "on_update": null, + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + } + ], + "table": "complex_calculations_skills", + "hash": "D2569EE39E2C9C58AAA3EB9E03A5EE726C5CD2F43D7387DA213F2C2D539F7606", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs b/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs new file mode 100644 index 00000000..19ded080 --- /dev/null +++ b/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs @@ -0,0 +1,69 @@ +defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationTables 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 + create table(:complex_calculations_skills, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :removed, :boolean, null: false, default: false + add :certification_id, :uuid + end + + create table(:complex_calculations_documentations, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :status, :text, null: false + add :documented_at, :utc_datetime_usec + add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()") + add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()") + + add :skill_id, + references(:complex_calculations_skills, + column: :id, + name: "complex_calculations_documentations_skill_id_fkey", + type: :uuid, + prefix: "public" + ) + end + + create table(:complex_calculations_certifications, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + end + + alter table(:complex_calculations_skills) do + modify :certification_id, + references(:complex_calculations_certifications, + column: :id, + name: "complex_calculations_skills_certification_id_fkey", + type: :uuid, + prefix: "public" + ) + end + end + + def down do + drop constraint( + :complex_calculations_skills, + "complex_calculations_skills_certification_id_fkey" + ) + + alter table(:complex_calculations_skills) do + modify :certification_id, :uuid + end + + drop table(:complex_calculations_certifications) + + drop constraint( + :complex_calculations_documentations, + "complex_calculations_documentations_skill_id_fkey" + ) + + drop table(:complex_calculations_documentations) + + drop table(:complex_calculations_skills) + end +end \ No newline at end of file diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs new file mode 100644 index 00000000..3efa2832 --- /dev/null +++ b/test/complex_calculations_test.exs @@ -0,0 +1,60 @@ +defmodule AshPostgres.Test.ComplexCalculationsTest do + use AshPostgres.RepoCase, async: false + + test "complex calculation" do + certification = + AshPostgres.Test.ComplexCalculations.Certification + |> Ash.Changeset.new() + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + skill = + AshPostgres.Test.ComplexCalculations.Skill + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:certification, certification, type: :append) + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + _documentation = + AshPostgres.Test.ComplexCalculations.Documentation + |> Ash.Changeset.new(%{status: :demonstrated}) + |> Ash.Changeset.manage_relationship(:skill, skill, type: :append) + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + skill = + skill + |> AshPostgres.Test.ComplexCalculations.Api.load!([:latest_documentation_status]) + + assert skill.latest_documentation_status == :demonstrated + + certification = + certification + |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :count_of_skills + ]) + + assert certification.count_of_skills == 1 + + certification = + certification + |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :count_of_approved_skills + ]) + + assert certification.count_of_approved_skills == 0 + + certification = + certification + |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :count_of_documented_skills + ]) + + assert certification.count_of_documented_skills == 1 + + certification = + certification + |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :some_documentation_created + ]) + + refute certification.some_documentation_created + end +end diff --git a/test/support/complex_calculations/api.ex b/test/support/complex_calculations/api.ex new file mode 100644 index 00000000..d7202c47 --- /dev/null +++ b/test/support/complex_calculations/api.ex @@ -0,0 +1,8 @@ +defmodule AshPostgres.Test.ComplexCalculations.Api do + @moduledoc false + use Ash.Api + + resources do + registry(AshPostgres.Test.ComplexCalculations.Registry) + end +end diff --git a/test/support/complex_calculations/registry.ex b/test/support/complex_calculations/registry.ex new file mode 100644 index 00000000..eed870d7 --- /dev/null +++ b/test/support/complex_calculations/registry.ex @@ -0,0 +1,10 @@ +defmodule AshPostgres.Test.ComplexCalculations.Registry do + @moduledoc false + use Ash.Registry + + entries do + entry(AshPostgres.Test.ComplexCalculations.Certification) + entry(AshPostgres.Test.ComplexCalculations.Skill) + entry(AshPostgres.Test.ComplexCalculations.Documentation) + end +end diff --git a/test/support/complex_calculations/resources/certification.ex b/test/support/complex_calculations/resources/certification.ex new file mode 100644 index 00000000..72de4f85 --- /dev/null +++ b/test/support/complex_calculations/resources/certification.ex @@ -0,0 +1,48 @@ +defmodule AshPostgres.Test.ComplexCalculations.Certification do + @moduledoc false + use Ash.Resource, data_layer: AshPostgres.DataLayer + + actions do + defaults([:create, :read, :update, :destroy]) + end + + aggregates do + count :count_of_documented_skills, :skills do + filter(expr(removed == false and status != :pending)) + end + + count :count_of_approved_skills, :skills do + filter(expr(removed == false and status == :approved)) + end + + count :count_of_skills, :skills do + filter(expr(removed == false)) + end + end + + attributes do + uuid_primary_key(:id) + end + + calculations do + calculate :all_documentation_approved, :boolean do + calculation(expr(count_of_skills == count_of_approved_skills)) + load([:count_of_skills, :count_of_approved_skills]) + end + + calculate :some_documentation_created, :boolean do + calculation(expr(count_of_documented_skills > 0 && all_documentation_approved == false)) + + load([:count_of_documented_skills, :all_documentation_approved]) + end + end + + postgres do + table "complex_calculations_certifications" + repo(AshPostgres.TestRepo) + end + + relationships do + has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill) + end +end diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex new file mode 100644 index 00000000..757f09bd --- /dev/null +++ b/test/support/complex_calculations/resources/documentation.ex @@ -0,0 +1,48 @@ +defmodule AshPostgres.Test.ComplexCalculations.Documentation do + @moduledoc false + use Ash.Resource, data_layer: AshPostgres.DataLayer + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + attribute( + :status, + :atom, + constraints: [ + one_of: [:demonstrated, :performed, :approved, :reopened] + ], + allow_nil?: false + ) + + attribute(:documented_at, :utc_datetime_usec) + create_timestamp(:inserted_at, private?: false) + update_timestamp(:updated_at, private?: false) + end + + calculations do + calculate( + :timestamp, + :utc_datetime_usec, + expr( + if is_nil(documented_at) do + inserted_at + else + documented_at + end + ) + ) + end + + postgres do + table "complex_calculations_documentations" + repo(AshPostgres.TestRepo) + end + + relationships do + belongs_to(:skill, AshPostgres.Test.ComplexCalculations.Skill) + end +end diff --git a/test/support/complex_calculations/resources/skill.ex b/test/support/complex_calculations/resources/skill.ex new file mode 100644 index 00000000..5a38037e --- /dev/null +++ b/test/support/complex_calculations/resources/skill.ex @@ -0,0 +1,50 @@ +defmodule AshPostgres.Test.ComplexCalculations.Skill do + @moduledoc false + use Ash.Resource, data_layer: AshPostgres.DataLayer + + actions do + defaults([:create, :read, :update, :destroy]) + end + + aggregates do + first :latest_documentation_status, [:documentations], :status do + sort(timestamp: :desc) + end + end + + attributes do + uuid_primary_key(:id) + attribute(:removed, :boolean, default: false, allow_nil?: false) + end + + calculations do + calculate :status, :atom do + calculation( + expr( + if is_nil(latest_documentation_status) do + :pending + else + latest_documentation_status + end + ) + ) + end + end + + postgres do + table "complex_calculations_skills" + repo(AshPostgres.TestRepo) + end + + relationships do + belongs_to(:certification, AshPostgres.Test.ComplexCalculations.Certification) + + has_many :documentations, AshPostgres.Test.ComplexCalculations.Documentation do + sort(timestamp: :desc, inserted_at: :desc) + end + + has_one :latest_documentation, AshPostgres.Test.ComplexCalculations.Documentation do + sort(timestamp: :desc, inserted_at: :desc) + end + end +end From d36457336bc82520846db7222632d46a4127e503 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 16 Aug 2023 23:02:56 -0400 Subject: [PATCH 0003/1215] fix: handle case where multiple grouped aggregates depend on further aggregates --- lib/aggregate.ex | 261 +++++++++++++---------------- test/complex_calculations_test.exs | 4 +- 2 files changed, 124 insertions(+), 141 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index a86b5087..af308fa4 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -50,109 +50,110 @@ defmodule AshPostgres.Aggregate do _ -> true end) - |> Enum.reduce_while({:ok, query, []}, fn {[first_relationship | relationship_path], - aggregates}, - {:ok, query, dynamics} -> - first_relationship = Ash.Resource.Info.relationship(resource, first_relationship) - is_single? = match?([_], aggregates) - - cond do - is_single? && - optimizable_first_aggregate?(resource, Enum.at(aggregates, 0)) -> - case add_first_join_aggregate(query, resource, hd(aggregates), root_data) do - {:ok, query, dynamic} -> - query = - if select? do - select_or_merge(query, hd(aggregates).name, dynamic) - else - query - end - - {:cont, {:ok, query, dynamics}} - - {:error, error} -> - {:halt, {:error, error}} - end - - is_single? && Enum.at(aggregates, 0).kind == :exists -> - [aggregate] = aggregates - - exists = - AshPostgres.Expr.dynamic_expr( - query, - %Ash.Query.Exists{path: aggregate.relationship_path, expr: true}, - query.__ash_bindings__ - ) - - {:cont, {:ok, query, [{aggregate.load, aggregate.name, exists} | dynamics]}} - - true -> - with {:ok, agg_root_query} <- - AshPostgres.Join.maybe_get_resource_query( - first_relationship.destination, - first_relationship, - query, - [first_relationship.name] - ), - agg_root_query <- - Map.update!( - agg_root_query, - :__ash_bindings__, - &Map.put(&1, :in_group?, true) - ), - {:ok, joined} <- - join_all_relationships( - agg_root_query, - aggregates, - relationship_path, - first_relationship, - is_single? - ), - {:ok, filtered} <- - maybe_filter_subquery( - joined, - first_relationship, - relationship_path, - aggregates, - is_single?, - source_binding - ), - with_subquery_select <- - select_all_aggregates( - aggregates, - filtered, - relationship_path, - query, - is_single?, - Ash.Resource.Info.related( + |> Enum.reduce_while( + {:ok, query, []}, + fn {[first_relationship | relationship_path], aggregates}, {:ok, query, dynamics} -> + first_relationship = Ash.Resource.Info.relationship(resource, first_relationship) + is_single? = match?([_], aggregates) + + cond do + is_single? && + optimizable_first_aggregate?(resource, Enum.at(aggregates, 0)) -> + case add_first_join_aggregate(query, resource, hd(aggregates), root_data) do + {:ok, query, dynamic} -> + query = + if select? do + select_or_merge(query, hd(aggregates).name, dynamic) + else + query + end + + {:cont, {:ok, query, dynamics}} + + {:error, error} -> + {:halt, {:error, error}} + end + + is_single? && Enum.at(aggregates, 0).kind == :exists -> + [aggregate] = aggregates + + exists = + AshPostgres.Expr.dynamic_expr( + query, + %Ash.Query.Exists{path: aggregate.relationship_path, expr: true}, + query.__ash_bindings__ + ) + + {:cont, {:ok, query, [{aggregate.load, aggregate.name, exists} | dynamics]}} + + true -> + with {:ok, agg_root_query} <- + AshPostgres.Join.maybe_get_resource_query( first_relationship.destination, - relationship_path - ) - ), - query <- - join_subquery( - query, - with_subquery_select, - first_relationship, - relationship_path, - aggregates, - source_binding - ) do - if select? do - new_dynamics = - Enum.map( - aggregates, - &{&1.load, &1.name, - select_dynamic(resource, query, &1, query.__ash_bindings__.current - 1)} - ) - - {:cont, {:ok, query, new_dynamics ++ dynamics}} - else - {:cont, {:ok, query, dynamics}} + first_relationship, + query, + [first_relationship.name] + ), + agg_root_query <- + Map.update!( + agg_root_query, + :__ash_bindings__, + &Map.put(&1, :in_group?, true) + ), + {:ok, joined} <- + join_all_relationships( + agg_root_query, + aggregates, + relationship_path, + first_relationship, + is_single? + ), + {:ok, filtered} <- + maybe_filter_subquery( + joined, + first_relationship, + relationship_path, + aggregates, + is_single?, + source_binding + ), + with_subquery_select <- + select_all_aggregates( + aggregates, + filtered, + relationship_path, + query, + is_single?, + Ash.Resource.Info.related( + first_relationship.destination, + relationship_path + ) + ), + query <- + join_subquery( + query, + with_subquery_select, + first_relationship, + relationship_path, + aggregates, + source_binding + ) do + if select? do + new_dynamics = + Enum.map( + aggregates, + &{&1.load, &1.name, + select_dynamic(resource, query, &1, query.__ash_bindings__.current - 1)} + ) + + {:cont, {:ok, query, new_dynamics ++ dynamics}} + else + {:cont, {:ok, query, dynamics}} + end end - end + end end - end) + ) case result do {:ok, query, dynamics} -> @@ -268,48 +269,25 @@ defmodule AshPostgres.Aggregate do end) end - defp maybe_filter_subquery( - agg_query, - _first_relationship, - _relationship_path, - [_aggregate1, _aggregate2 | _rest], - false, - _source_binding - ) do - {:ok, agg_query} - end - defp maybe_filter_subquery( agg_query, first_relationship, relationship_path, - [aggregate], - true, - source_binding - ) do - apply_agg_query( - agg_query, - aggregate, - relationship_path, - first_relationship, - source_binding - ) - end - - defp apply_agg_query( - agg_query, - aggregate, - relationship_path, - first_relationship, + aggregates, + is_single?, source_binding ) do - if has_filter?(aggregate.query) do + Enum.reduce_while(aggregates, {:ok, agg_query}, fn aggregate, {:ok, agg_query} -> filter = - Ash.Filter.move_to_relationship_path( - aggregate.query.filter, - relationship_path - ) - |> Map.put(:resource, first_relationship.destination) + if aggregate.query.filter do + Ash.Filter.move_to_relationship_path( + aggregate.query.filter, + relationship_path + ) + |> Map.put(:resource, first_relationship.destination) + else + aggregate.query.filter + end used_calculations = Ash.Filter.used_calculations( @@ -354,14 +332,17 @@ defmodule AshPostgres.Aggregate do source_binding ) do {:ok, agg_query} -> - AshPostgres.DataLayer.filter(agg_query, filter, agg_query.__ash_bindings__.resource) + if has_filter?(aggregate.query) && is_single? do + {:cont, + AshPostgres.DataLayer.filter(agg_query, filter, agg_query.__ash_bindings__.resource)} + else + {:cont, {:ok, agg_query}} + end other -> - other + {:halt, other} end - else - {:ok, agg_query} - end + end) end defp join_subquery( diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 3efa2832..6c5535ce 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -52,9 +52,11 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do certification = certification |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :count_of_documented_skills, + :all_documentation_approved, :some_documentation_created ]) - refute certification.some_documentation_created + assert certification.some_documentation_created end end From c8aa2a8dd8d542b56cb0110868c7c5574445b2ad Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 18 Aug 2023 11:22:01 -0400 Subject: [PATCH 0004/1215] improvement: add `value_to_postgres_default/3` and `AshPostgres.Type` --- lib/migration.ex | 6 ++-- .../migration_generator.ex | 35 +++++++++++++++++-- lib/type.ex | 18 ++++++++++ lib/types/ci_string_wrapper copy.ex | 2 +- lib/types/ci_string_wrapper.ex | 2 +- mix.exs | 2 +- mix.lock | 4 +-- test/support/types/point.ex | 2 +- 8 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 lib/type.ex diff --git a/lib/migration.ex b/lib/migration.ex index b5195af3..78e76f97 100644 --- a/lib/migration.ex +++ b/lib/migration.ex @@ -42,11 +42,11 @@ defmodule AshPostgres.Migration do Keep in mind, that if you want to create a custom enum type, you will want to add ```elixir - def storage_type, do: :my_type_name + def storage_type(_), do: :my_type_name ``` """ - def create_enum(type) do - if type.storage_type() == :string do + def create_enum(type, constraints \\ []) do + if type.storage_type(constraints) == :string do raise "Must customize the storage_type for #{type} in order to create an enum" end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 67386ca2..94ab7351 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2735,7 +2735,7 @@ defmodule AshPostgres.MigrationGenerator do Ash.Type.NewType.constraints(type, constraints) ) else - migration_type_from_storage_type(Ash.Type.storage_type(other)) + migration_type_from_storage_type(Ash.Type.storage_type(other, constraints)) end end @@ -2829,8 +2829,37 @@ defmodule AshPostgres.MigrationGenerator do defp default(%{name: name, default: default}, resource, _) when default == %{}, do: configured_default(resource, name) || "%{}" - defp default(%{name: name, default: value}, resource, _), - do: configured_default(resource, name) || EctoMigrationDefault.to_default(value) + defp default(%{name: name, default: value, type: type} = attr, resource, _) do + case configured_default(resource, name) do + nil -> + case migration_default(type, Map.get(attr, :constraints, []), value) do + {:ok, default} -> + default + + :error -> + EctoMigrationDefault.to_default(value) + end + + default -> + default + end + end + + defp migration_default(type, constraints, value) do + type = + type + |> unwrap_type() + |> Ash.Type.get_type() + + if function_exported?(type, :value_to_postgres_default, 3) do + type.value_to_postgres_default(type, constraints, value) + else + :error + end + end + + defp unwrap_type({:array, type}), do: unwrap_type(type) + defp unwrap_type(type), do: type defp configured_default(resource, attribute) do AshPostgres.DataLayer.Info.migration_defaults(resource)[attribute] diff --git a/lib/type.ex b/lib/type.ex new file mode 100644 index 00000000..7b8ca8c2 --- /dev/null +++ b/lib/type.ex @@ -0,0 +1,18 @@ +defmodule AshPostgres.Type do + @moduledoc """ + Postgres specific callbacks for `Ash.Type`. + + Use this in addition to `Ash.Type`. + """ + + @callback value_to_postgres_default(Ash.Type.t(), Ash.Type.constraints(), term) :: + {:ok, String.t()} | :error + + defmacro __using__(_) do + quote do + def value_to_postgres_default(_, _, _), do: :error + + defoverridable value_to_postgres_default: 3 + end + end +end diff --git a/lib/types/ci_string_wrapper copy.ex b/lib/types/ci_string_wrapper copy.ex index 4abe3ca6..72f850d4 100644 --- a/lib/types/ci_string_wrapper copy.ex +++ b/lib/types/ci_string_wrapper copy.ex @@ -3,7 +3,7 @@ defmodule Ash.Type.CiStringWrapper do use Ash.Type @impl true - def storage_type, do: :citext + def storage_type(_), do: :citext @impl true defdelegate cast_input(value, constraints), to: Ash.Type.CiString diff --git a/lib/types/ci_string_wrapper.ex b/lib/types/ci_string_wrapper.ex index e3714300..82d067da 100644 --- a/lib/types/ci_string_wrapper.ex +++ b/lib/types/ci_string_wrapper.ex @@ -3,7 +3,7 @@ defmodule Ash.Type.StringWrapper do use Ash.Type @impl true - def storage_type, do: :text + def storage_type(_), do: :text @impl true defdelegate cast_input(value, constraints), to: Ash.Type.String diff --git a/mix.exs b/mix.exs index 8448d0ff..c5750197 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.13")}, + {:ash, ash_version("~> 2.14 and >= 2.14.2")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 87e1811d..85baada2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.13.3", "0ab95342148636c97833224b67543a9652b5bb1fe14cf5cd30155da772f0c0fd", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "42fb2cbaac7e4a98e56adebbf97e641135bef4e73129c1148d42e93cbf59a3a0"}, + "ash": {:hex, :ash, "2.14.2", "111829b1db52e43c28c0660b2da8f1bfc76199dd8e15289f86f5998c07fb8d36", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e6e5924ccb3f8b0ab5de15b22006a4a18c0f002aadadd7aa9dfa39d53cff0b9a"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -34,7 +34,7 @@ "postgrex": {:hex, :postgrex, "0.17.0", "1ea81cb0820079bcedd880379357cfc9faf70bc8fee3a87054b13fcb646c6150", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ea670562ed62a5ca40aa4689290167a97b0712f59f7e318d6ce9eb253f52e02e"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, - "spark": {:hex, :spark, "1.1.21", "8d09983e628d26edf358e7e8c0fa922667a049c4cfea6895d4b0820fc9bc1261", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "588dda298d6ea0a1d3f06c7978145ee799281bf8bcb0b418b7dae4b3d0be2e59"}, + "spark": {:hex, :spark, "1.1.22", "68ba00f9acb4c8bc2c93ef82249493687ddf0f0a4f7e79c3c0e22b06719add56", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b798b95990eed8f2409df47b818b5dbcd00e9b5c30d0355465d0b04bbf9b5c4c"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/support/types/point.ex b/test/support/types/point.ex index b22d36f4..ea3db339 100644 --- a/test/support/types/point.ex +++ b/test/support/types/point.ex @@ -2,7 +2,7 @@ defmodule AshPostgres.Test.Point do @moduledoc false use Ash.Type - def storage_type, do: {:array, :float} + def storage_type(_), do: {:array, :float} def cast_input(nil, _), do: {:ok, nil} From 8a6f51efb50eaf8bbbd44bcf7b3a23381dc0fe1f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 21 Aug 2023 11:14:33 -0400 Subject: [PATCH 0005/1215] improvement: specify @behaviour in AshPostgres.Type --- lib/type.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/type.ex b/lib/type.ex index 7b8ca8c2..4f6343d0 100644 --- a/lib/type.ex +++ b/lib/type.ex @@ -10,6 +10,7 @@ defmodule AshPostgres.Type do defmacro __using__(_) do quote do + @behaviour AshPostgres.Type def value_to_postgres_default(_, _, _), do: :error defoverridable value_to_postgres_default: 3 From 99b2a5631c275d3fe302d38d0781ccb453dc9256 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Aug 2023 14:51:31 -0400 Subject: [PATCH 0006/1215] improvement: support in-line aggregates fix: support non-atom named aggregates --- lib/aggregate.ex | 50 ++++++++++++++++++++++++++++++++-- lib/data_layer.ex | 2 ++ lib/expr.ex | 27 ++++++++++++++++-- lib/types/types.ex | 3 ++ mix.exs | 2 +- mix.lock | 4 +-- test/aggregate_test.exs | 30 ++++++++++++++++++++ test/calculation_test.exs | 40 +++++++++++++++++++++++++++ test/support/resources/post.ex | 17 ++++++++++++ 9 files changed, 167 insertions(+), 8 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index af308fa4..2413c879 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -4,6 +4,10 @@ defmodule AshPostgres.Aggregate do import Ecto.Query, only: [from: 2, subquery: 1] require Ecto.Query + @next_aggregate_names Enum.reduce(0..999, %{}, fn i, acc -> + Map.put(acc, :"aggregate_#{i}", :"aggregate_#{i + 1}") + end) + def add_aggregates( query, aggregates, @@ -20,6 +24,19 @@ defmodule AshPostgres.Aggregate do {:ok, aggregates} -> query = AshPostgres.DataLayer.default_bindings(query, resource) + {query, aggregates, aggregate_name_mapping} = + Enum.reduce(aggregates, {query, [], %{}}, fn aggregate, + {query, aggregates, aggregate_name_mapping} -> + if is_atom(aggregate.name) do + {query, [aggregate | aggregates], aggregate_name_mapping} + else + {query, name} = use_aggregate_name(query, aggregate.name) + + {query, [%{aggregate | name: name} | aggregates], + Map.put(aggregate_name_mapping, name, aggregate.name)} + end + end) + aggregates = Enum.reject(aggregates, fn aggregate -> Map.has_key?(query.__ash_bindings__.aggregate_defs, aggregate.name) @@ -157,7 +174,7 @@ defmodule AshPostgres.Aggregate do case result do {:ok, query, dynamics} -> - {:ok, add_aggregate_selects(query, dynamics)} + {:ok, add_aggregate_selects(query, dynamics, aggregate_name_mapping)} {:error, error} -> {:error, error} @@ -168,6 +185,23 @@ defmodule AshPostgres.Aggregate do end end + defp use_aggregate_name(query, aggregate_name) do + {%{ + query + | __ash_bindings__: %{ + query.__ash_bindings__ + | current_aggregate_name: + next_aggregate_name(query.__ash_bindings__.current_aggregate_name), + aggregate_names: + Map.put( + query.__ash_bindings__.aggregate_names, + aggregate_name, + query.__ash_bindings__.current_aggregate_name + ) + } + }, query.__ash_bindings__.current_aggregate_name} + end + defp resource_aggregates_to_aggregates(resource, aggregates) do aggregates |> Enum.reduce_while({:ok, []}, fn @@ -490,6 +524,16 @@ defmodule AshPostgres.Aggregate do ) end + def next_aggregate_name(i) do + @next_aggregate_names[i] || + raise Ash.Error.Framework.AssumptionFailed, + message: """ + All 1000 static names for aggregates have been used in a single query. + Congratulations, this means that you have gone so wildly beyond our imagination + of how much can fit into a single quer. Please file an issue and we will raise the limit. + """ + end + defp subquery_if_distinct(%{distinct: nil} = query), do: query defp subquery_if_distinct(subquery) do @@ -597,7 +641,7 @@ defmodule AshPostgres.Aggregate do end) end - defp add_aggregate_selects(query, dynamics) do + defp add_aggregate_selects(query, dynamics, name_mapping) do {in_aggregates, in_body} = Enum.split_with(dynamics, fn {load, _name, _dynamic} -> is_nil(load) end) @@ -615,7 +659,7 @@ defmodule AshPostgres.Aggregate do aggs, :aggregates, Map.new(in_aggregates, fn {_, name, dynamic} -> - {name, dynamic} + {name_mapping[name] || name, dynamic} end) ) end diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e186dbb2..ee602ba8 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2194,6 +2194,8 @@ defmodule AshPostgres.DataLayer do calculations: %{}, parent_resources: [], aggregate_defs: %{}, + current_aggregate_name: :aggregate_0, + aggregate_names: %{}, context: context, bindings: %{start_bindings => %{path: [], type: :root, source: resource}} }) diff --git a/lib/expr.ex b/lib/expr.ex index 720ee416..95c28870 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -517,12 +517,18 @@ defmodule AshPostgres.Expr do {params, [{:raw, str} | fragment_data], count} {:casted_expr, dynamic}, {params, fragment_data, count} -> - {[{dynamic, :any} | params], [{:expr, {:^, [], [count]}} | fragment_data], count + 1} + {item, params, count} = + {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} + + {params, [{:expr, item} | fragment_data], count} {:expr, expr}, {params, fragment_data, count} -> dynamic = do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?) - {[{dynamic, :any} | params], [{:expr, {:^, [], [count]}} | fragment_data], count + 1} + {item, params, count} = + {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} + + {params, [{:expr, item} | fragment_data], count} end) %Ecto.Query.DynamicExpr{ @@ -824,6 +830,16 @@ defmodule AshPostgres.Expr do _embedded?, _type ) do + %{attribute: aggregate} = + ref = + case bindings.aggregate_names[aggregate.name] do + nil -> + ref + + name -> + %{ref | attribute: %{aggregate | name: name}} + end + related = Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path) first_optimized_aggregate? = @@ -1246,6 +1262,13 @@ defmodule AshPostgres.Expr do end end + defp do_dynamic_expr(query, value, bindings, embedded?, _type) + when is_map(value) and not is_struct(value) do + Map.new(value, fn {key, value} -> + {key, do_dynamic_expr(query, value, bindings, embedded?)} + end) + end + defp do_dynamic_expr(query, other, bindings, true, type) do if other && is_atom(other) && !is_boolean(other) do to_string(other) diff --git a/lib/types/types.ex b/lib/types/types.ex index 699b31d2..87200426 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -29,6 +29,9 @@ defmodule AshPostgres.Types do parameterized_type(Ash.Type.StringWrapper, constraints) end + def parameterized_type(type, _constraints) when type in [Ash.Type.Map, Ash.Type.Map.EctoType], + do: nil + def parameterized_type(type, constraints) do if Ash.Type.ash_type?(type) do cast_in_query? = diff --git a/mix.exs b/mix.exs index c5750197..f1fac65b 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.14 and >= 2.14.2")}, + {:ash, ash_version("~> 2.14 and >= 2.14.3")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 85baada2..b9e0c1f8 100644 --- a/mix.lock +++ b/mix.lock @@ -9,7 +9,7 @@ "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, + "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, @@ -31,7 +31,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, - "postgrex": {:hex, :postgrex, "0.17.0", "1ea81cb0820079bcedd880379357cfc9faf70bc8fee3a87054b13fcb646c6150", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ea670562ed62a5ca40aa4689290167a97b0712f59f7e318d6ce9eb253f52e02e"}, + "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, "spark": {:hex, :spark, "1.1.22", "68ba00f9acb4c8bc2c93ef82249493687ddf0f0a4f7e79c3c0e22b06719add56", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b798b95990eed8f2409df47b818b5dbcd00e9b5c30d0355465d0b04bbf9b5c4c"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index fbb88d94..78e420f2 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -109,6 +109,36 @@ defmodule AshPostgres.AggregateTest do |> Api.read_one!() end + test "with data and a custom string keyed aggregate, it returns the count" do + post = + Post + |> Ash.Changeset.new(%{title: "title"}) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + Comment + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + import Ash.Query + + assert %{aggregates: %{"custom_count_of_comments" => 1}} = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.aggregate( + "custom_count_of_comments", + :count, + :comments, + query: [filter: expr(not is_nil(title))] + ) + |> Api.read_one!() + end + test "with data for a many_to_many, it returns the count" do post = Post diff --git a/test/calculation_test.exs b/test/calculation_test.exs index a4f8cc38..0f02d653 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -446,6 +446,46 @@ defmodule AshPostgres.CalculationTest do end end + describe "maps" do + test "maps can reference filtered aggregats" do + post = + Post + |> Ash.Changeset.new(%{title: "match", score: 42}) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "foo", likes: 2}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "foo", likes: 2}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "bar", likes: 2}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + assert [%{agg_map: %{called_foo: 2, called_bar: 1}}] = + Post + |> Ash.Query.load(:agg_map) + |> Api.read!() + end + + test "maps can be constructed" do + Post + |> Ash.Changeset.new(%{title: "match", score: 42}) + |> Api.create!() + + assert [%{score_map: %{negative_score: %{foo: -42}}}] = + Post + |> Ash.Query.load(:score_map) + |> Api.read!() + end + end + describe "at/2" do test "selects items by index" do author = diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 5a934fb2..93d5d593 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -147,6 +147,23 @@ defmodule AshPostgres.Test.Post do calculate(:category_label, :ci_string, expr("(" <> category <> ")")) calculate(:score_with_score, :string, expr(score <> score)) + calculate( + :score_map, + :map, + expr(%{ + negative_score: %{foo: negative_score, bar: negative_score} + }) + ) + + calculate( + :agg_map, + :map, + expr(%{ + called_foo: count(comments, query: [filter: expr(title == "foo")]), + called_bar: count(comments, query: [filter: expr(title == "bar")]) + }) + ) + calculate(:c_times_p, :integer, expr(count_of_comments * count_of_linked_posts), load: [:count_of_comments, :count_of_linked_posts] ) From e124e0b4e419617445f68492d089a3d92f8bc879 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Aug 2023 14:54:15 -0400 Subject: [PATCH 0007/1215] chore: release version v1.3.42 --- CHANGELOG.md | 21 +++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a30770..56fa4ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.42](https://github.com/ash-project/ash_postgres/compare/v1.3.41...v1.3.42) (2023-08-22) + + + + +### Bug Fixes: + +* support non-atom named aggregates + +* handle case where multiple grouped aggregates depend on further aggregates + +### Improvements: + +* support in-line aggregates + +* specify @behaviour in AshPostgres.Type + +* add `value_to_postgres_default/3` and `AshPostgres.Type` + +* handle non-cast-in-type queries + ## [v1.3.41](https://github.com/ash-project/ash_postgres/compare/v1.3.40...v1.3.41) (2023-08-08) diff --git a/mix.exs b/mix.exs index f1fac65b..a0c58f22 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.41" + @version "1.3.42" def project do [ From 36d83d06719cd8bdec196a05d0ca8c83b4c7007d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Aug 2023 14:54:46 -0400 Subject: [PATCH 0008/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index b9e0c1f8..755d3516 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.2", "111829b1db52e43c28c0660b2da8f1bfc76199dd8e15289f86f5998c07fb8d36", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e6e5924ccb3f8b0ab5de15b22006a4a18c0f002aadadd7aa9dfa39d53cff0b9a"}, + "ash": {:hex, :ash, "2.14.3", "ebe59b9d390a9c488a30ce7a06c6aeb154845517186d94535c5da2a2753ed20b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6366bf0950fd2e6409bc820a9f8046e21086fa73741ed8bf94191d40e6aa1c91"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 8fc0f8149c9b32b3711c92c5ddbd083e0a1b0a55 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Aug 2023 15:29:33 -0400 Subject: [PATCH 0009/1215] fix: properly provide constraints on all type casting --- lib/expr.ex | 31 +++++++++++++++++++------------ lib/sort.ex | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 95c28870..6afcb94e 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1232,7 +1232,13 @@ defmodule AshPostgres.Expr do defp do_dynamic_expr( query, - %Ref{attribute: %Ash.Resource.Attribute{name: name, type: attr_type}} = ref, + %Ref{ + attribute: %Ash.Resource.Attribute{ + name: name, + type: attr_type, + constraints: constraints + } + } = ref, bindings, _embedded?, expr_type @@ -1243,7 +1249,12 @@ defmodule AshPostgres.Expr do raise "Error while building reference: #{inspect(ref)}" end - case AshPostgres.Types.parameterized_type(attr_type || expr_type, []) do + constraints = + if attr_type do + constraints + end + + case AshPostgres.Types.parameterized_type(attr_type || expr_type, constraints) do nil -> if query.__ash_bindings__[:parent?] do Ecto.Query.dynamic(field(parent_as(^ref_binding), ^name)) @@ -1312,10 +1323,13 @@ defmodule AshPostgres.Expr do else case maybe_sanitize_list(query, value, bindings, true, type) do ^value -> - type = AshPostgres.Types.parameterized_type(type, []) - validate_type!(query, type, value) + if type do + validate_type!(query, type, value) - Ecto.Query.dynamic(type(^value, ^type)) + Ecto.Query.dynamic(type(^value, ^type)) + else + value + end value -> value @@ -1404,7 +1418,6 @@ defmodule AshPostgres.Expr do defp maybe_type(dynamic, nil, _query), do: dynamic defp maybe_type(dynamic, type, query) do - type = AshPostgres.Types.parameterized_type(type, []) validate_type!(query, type, type) Ecto.Query.dynamic(type(^dynamic, ^type)) @@ -1467,12 +1480,6 @@ defmodule AshPostgres.Expr do ) if type do - type = - AshPostgres.Types.parameterized_type( - type, - [] - ) - validate_type!(query, type, get_path) Ecto.Query.dynamic(type(^expr, ^type)) diff --git a/lib/sort.ex b/lib/sort.ex index f220b194..a67a1d56 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -55,7 +55,7 @@ defmodule AshPostgres.Sort do {order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr} -> type = if calc.type do - AshPostgres.Types.parameterized_type(calc.type, []) + AshPostgres.Types.parameterized_type(calc.type, calc.constraints) else nil end From 1b454f9ced5197cea935231457f7c76f1d951d72 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Aug 2023 15:29:46 -0400 Subject: [PATCH 0010/1215] chore: release version v1.3.43 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56fa4ac4..9b2445cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.43](https://github.com/ash-project/ash_postgres/compare/v1.3.42...v1.3.43) (2023-08-22) + + + + +### Bug Fixes: + +* properly provide constraints on all type casting + ## [v1.3.42](https://github.com/ash-project/ash_postgres/compare/v1.3.41...v1.3.42) (2023-08-22) diff --git a/mix.exs b/mix.exs index a0c58f22..4f77a4a2 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.42" + @version "1.3.43" def project do [ From cbde3958f3d286f58ba4e7639cf110b40fdc484b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 23 Aug 2023 12:54:25 -0400 Subject: [PATCH 0011/1215] fix: properly handle ensure nsted calls to `get_path` are jsonb --- lib/expr.ex | 30 +- .../test_repo/posts/20230823161017.json | 289 ++++++++++++++++++ .../20230823161017_migrate_resources10.exs | 21 ++ test/calculation_test.exs | 12 + test/support/resources/post.ex | 2 + 5 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20230823161017.json create mode 100644 priv/test_repo/migrations/20230823161017_migrate_resources10.exs diff --git a/lib/expr.ex b/lib/expr.ex index 6afcb94e..765324b7 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -525,8 +525,15 @@ defmodule AshPostgres.Expr do {:expr, expr}, {params, fragment_data, count} -> dynamic = do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?) + type = + if is_binary(expr) do + :string + else + :any + end + {item, params, count} = - {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} + {{:^, [], [count]}, [{dynamic, type} | params], count + 1} {params, [{:expr, item} | fragment_data], count} end) @@ -1462,18 +1469,25 @@ defmodule AshPostgres.Expr do ) do path = Enum.map(right, &to_string/1) + path_frags = + path + |> Enum.flat_map(fn item -> + [expr: item, raw: "::text,"] + end) + |> :lists.droplast() + |> Enum.concat(raw: "::text)") + expr = do_dynamic_expr( query, %Fragment{ embedded?: pred_embedded?, - arguments: [ - raw: "(", - expr: left, - raw: " #>> ", - expr: path, - raw: ")" - ] + arguments: + [ + raw: "jsonb_extract_path_text(", + expr: left, + raw: "::jsonb," + ] ++ path_frags }, bindings, embedded? diff --git a/priv/resource_snapshots/test_repo/posts/20230823161017.json b/priv/resource_snapshots/test_repo/posts/20230823161017.json new file mode 100644 index 00000000..dd0781f2 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20230823161017.json @@ -0,0 +1,289 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "destination_attribute": "id", + "primary_key?": true, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "destination_attribute": "id", + "primary_key?": true, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "B039FC2340F75D92FE19FC2E8682AAAE08D942D413D981CC61ECA1FE8EBCCFF8", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "where": null, + "unique": true, + "concurrently": true, + "using": null + } + ], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20230823161017_migrate_resources10.exs b/priv/test_repo/migrations/20230823161017_migrate_resources10.exs new file mode 100644 index 00000000..314d7bea --- /dev/null +++ b/priv/test_repo/migrations/20230823161017_migrate_resources10.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources10 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(:posts) do + add :stuff, :map + end + end + + def down do + alter table(:posts) do + remove :stuff + end + end +end \ No newline at end of file diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 0f02d653..7c7c38e3 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -518,6 +518,18 @@ defmodule AshPostgres.CalculationTest do ) end + test "nested get_path works" do + Logger.configure(level: :debug) + + assert "thing" = + Post + |> Ash.Changeset.new(%{title: "match", price: 10_024, stuff: %{foo: %{bar: "thing"}}}) + |> Ash.Changeset.deselect(:stuff) + |> Api.create!() + |> Api.load!(:foo_bar_from_stuff) + |> Map.get(:foo_bar_from_stuff) + end + test "runtime expression calcs" do author = Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 93d5d593..bc7c86be 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -80,6 +80,7 @@ defmodule AshPostgres.Test.Post do attribute(:status_enum, AshPostgres.Test.Types.StatusEnum) attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast, source: :status_enum) attribute(:point, AshPostgres.Test.Point) + attribute(:stuff, :map) attribute(:uniq_one, :string) attribute(:uniq_two, :string) attribute(:uniq_custom_one, :string) @@ -146,6 +147,7 @@ defmodule AshPostgres.Test.Post do calculate(:negative_score, :integer, expr(-score)) calculate(:category_label, :ci_string, expr("(" <> category <> ")")) calculate(:score_with_score, :string, expr(score <> score)) + calculate(:foo_bar_from_stuff, :string, expr(stuff[:foo][:bar])) calculate( :score_map, From 5a4a52854ba5d3b00b62795ae9b288404d191bcd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 28 Aug 2023 16:18:56 -0400 Subject: [PATCH 0012/1215] improvement: support atomics (#165) --- lib/data_layer.ex | 242 ++++++++++++++++++++++++++++----- lib/expr.ex | 4 +- mix.exs | 2 +- mix.lock | 4 +- test/atomics_test.exs | 59 ++++++++ test/support/resources/post.ex | 7 + 6 files changed, 279 insertions(+), 39 deletions(-) create mode 100644 test/atomics_test.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ee602ba8..7510610b 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -482,6 +482,7 @@ defmodule AshPostgres.DataLayer do def can?(_, :transact), do: true def can?(_, :composite_primary_key), do: true + def can?(_, {:atomic, :update}), do: true def can?(_, :upsert), do: true def can?(_, :changeset_filter), do: true @@ -1327,16 +1328,32 @@ defmodule AshPostgres.DataLayer do Map.get(changeset, :filters, %{}) end + filters = + if changeset.action_type == :create do + filters + else + changeset.resource + |> Ash.Resource.Info.primary_key() + |> Enum.reduce(filters, fn key, filters -> + Map.put(filters, key, Map.get(record, key)) + end) + end + attributes = changeset.resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) + attributes_to_change = + Enum.reject(attributes, fn attribute -> + Keyword.has_key?(changeset.atomics, attribute) + end) + ecto_changeset = record |> to_ecto() |> set_table(changeset, type) - |> Ecto.Changeset.change(Map.take(changeset.attributes, attributes)) + |> Ecto.Changeset.change(Map.take(changeset.attributes, attributes_to_change)) |> Map.update!(:filters, &Map.merge(&1, filters)) |> add_configured_foreign_key_constraints(record.__struct__) |> add_unique_indexes(record.__struct__, changeset) @@ -1423,7 +1440,42 @@ defmodule AshPostgres.DataLayer do ) end - defp handle_raised_error(error, stacktrace, _context, _resource) do + defp handle_raised_error( + %Postgrex.Error{} = error, + stacktrace, + %{constraints: user_constraints}, + _resource + ) do + case Ecto.Adapters.Postgres.Connection.to_constraints(error, []) do + [{type, constraint}] -> + user_constraint = + Enum.find(user_constraints, fn c -> + case {c.type, c.constraint, c.match} do + {^type, ^constraint, :exact} -> true + {^type, cc, :suffix} -> String.ends_with?(constraint, cc) + {^type, cc, :prefix} -> String.starts_with?(constraint, cc) + {^type, %Regex{} = r, _match} -> Regex.match?(r, constraint) + _ -> false + end + end) + + case user_constraint do + %{field: field, error_message: error_message, error_type: error_type} -> + {:error, + to_ash_error( + {field, {error_message, [constraint: error_type, constraint_name: constraint]}} + )} + + nil -> + reraise error, stacktrace + end + + _ -> + reraise error, stacktrace + end + end + + defp handle_raised_error(error, stacktrace, _ecto_changeset, _resource) do {:error, Ash.Error.to_ash_error(error, stacktrace)} end @@ -1812,46 +1864,170 @@ defmodule AshPostgres.DataLayer do @impl true def update(resource, changeset) do - changeset.data - |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :update) - |> dynamic_repo(resource, changeset).update( - repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - ) - |> from_ecto() - |> handle_errors() - |> case do - {:ok, result} -> - maybe_update_tenant(resource, changeset, result) + ecto_changeset = + changeset.data + |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) + |> ecto_changeset(changeset, :update) - {:ok, result} + try do + attr_names = + resource + |> Ash.Resource.Info.attributes() + |> Enum.map(& &1.name) - {:error, error} -> - {:error, error} + query = from(row in resource, as: ^0) + + query = + query + |> default_bindings(resource, changeset.context) + |> Ecto.Query.select(^attr_names) + + query = + Enum.reduce(ecto_changeset.filters, query, fn {key, value}, query -> + from(row in query, + where: field(row, ^key) == ^value + ) + end) + + set = + ecto_changeset.changes + |> Map.take(attr_names) + |> Map.to_list() + + atomics_result = + Enum.reduce_while(changeset.atomics, {:ok, query, set}, fn {field, expr}, + {:ok, query, set} -> + used_calculations = + Ash.Filter.used_calculations( + expr, + resource + ) + + used_aggregates = + expr + |> AshPostgres.Aggregate.used_aggregates( + resource, + used_calculations, + [] + ) + |> Enum.map(fn aggregate -> + %{aggregate | load: aggregate.name} + end) + + with {:ok, query} <- + AshPostgres.Join.join_all_relationships( + query, + %Ash.Filter{ + resource: resource, + expression: expr + }, + left_only?: true + ), + {:ok, query} <- + AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0), + dynamic <- + AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__) do + {:cont, {:ok, query, Keyword.put(set, field, dynamic)}} + else + other -> + {:halt, other} + end + end) + + case atomics_result do + {:ok, query, set} -> + {params, set, _} = + Enum.reduce(set, {[], [], 0}, fn {key, value}, {params, set, count} -> + case AshPostgres.Expr.dynamic_expr(query, value, query.__ash_bindings__) do + %Ecto.Query.DynamicExpr{} = dynamic -> + result = + Ecto.Query.Builder.Dynamic.partially_expand( + :select, + query, + dynamic, + params, + count + ) + + expr = elem(result, 0) + new_params = elem(result, 1) + + new_count = + result |> Tuple.to_list() |> List.last() + + {new_params, [{key, expr} | set], new_count} + + other -> + {[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} + end + end) + + query = + Map.put(query, :updates, [ + %Ecto.Query.QueryExpr{ + # why do I have to reverse the `set`??? + # it breaks if I don't + expr: [set: Enum.reverse(set)], + params: Enum.reverse(params) + } + ]) + + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + + repo_opts = + Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + + result = + dynamic_repo(resource, changeset).update_all( + query, + [], + repo_opts + ) + + case result do + {0, []} -> + {:error, + Ash.Error.Changes.StaleRecord.exception( + resource: resource, + filters: ecto_changeset.filters + )} + + {1, [record]} -> + maybe_update_tenant(resource, changeset, record) + + {:ok, record} + end + + {:error, error} -> + {:error, error} + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, changeset, resource) end @impl true def destroy(resource, %{data: record} = changeset) do - record - |> ecto_changeset(changeset, :delete) - |> dynamic_repo(resource, changeset).delete( - repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - ) - |> from_ecto() - |> case do - {:ok, _record} -> - :ok + ecto_changeset = ecto_changeset(record, changeset, :delete) - {:error, error} -> - handle_errors({:error, error}) + try do + ecto_changeset + |> dynamic_repo(resource, changeset).delete( + repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + ) + |> from_ecto() + |> case do + {:ok, _record} -> + :ok + + {:error, error} -> + handle_errors({:error, error}) + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, changeset, resource) end @impl true diff --git a/lib/expr.ex b/lib/expr.ex index 765324b7..d1c4952b 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1376,11 +1376,9 @@ defmodule AshPostgres.Expr do end end) - exprs = Enum.reverse(exprs) - %Ecto.Query.DynamicExpr{ fun: fn _query -> - {exprs, Enum.reverse(params), [], []} + {Enum.reverse(exprs), Enum.reverse(params), [], []} end, binding: [], file: __ENV__.file, diff --git a/mix.exs b/mix.exs index 4f77a4a2..15fda552 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.14 and >= 2.14.3")}, + {:ash, ash_version(github: "ash-project/ash")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 755d3516..1abc0408 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.3", "ebe59b9d390a9c488a30ce7a06c6aeb154845517186d94535c5da2a2753ed20b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6366bf0950fd2e6409bc820a9f8046e21086fa73741ed8bf94191d40e6aa1c91"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "18cb24e7f720282d46ad6aed7ba065d9cd71604c", []}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -33,7 +33,7 @@ "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, - "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, + "sourceror": {:hex, :sourceror, "0.13.0", "c6ecc96ee3ae0e042e9082a9550a1989ea40182492dc29024a8d9d2b136e5014", [:mix], [], "hexpm", "d0a819491061cd26bfa4450d1c84301a410c19c1782a6577ce15853fc0e7e4e1"}, "spark": {:hex, :spark, "1.1.22", "68ba00f9acb4c8bc2c93ef82249493687ddf0f0a4f7e79c3c0e22b06719add56", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b798b95990eed8f2409df47b818b5dbcd00e9b5c30d0355465d0b04bbf9b5c4c"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs new file mode 100644 index 00000000..2f8b76d5 --- /dev/null +++ b/test/atomics_test.exs @@ -0,0 +1,59 @@ +defmodule AshPostgres.AtomicsTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.{Api, Post} + + import Ash.Expr + + test "a basic atomic works" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Api.create!() + + assert %{price: 2} = + post + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.atomic(:price, expr(price + 1)) + |> Api.update!() + end + + test "an atomic that violates a constraint will return the proper error" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Api.create!() + + assert_raise Ash.Error.Invalid, ~r/does not exist/, fn -> + post + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.atomic(:organization_id, Ash.UUID.generate()) + |> Api.update!() + end + end + + test "an atomic can refer to a calculation" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Api.create!() + + post = + post + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.atomic(:score, expr(score_after_winning)) + |> Api.update!() + + assert post.score == 1 + end + + test "an atomic can be attached to an action" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Api.create!() + + assert Post.increment_score!(post, 2).score == 2 + + assert Post.increment_score!(post, 2).score == 4 + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index bc7c86be..6ab5a364 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -61,6 +61,11 @@ defmodule AshPostgres.Test.Post do ) ) end + + update :increment_score do + argument(:amount, :integer, default: 1) + set(:score, expr((score || 0) + ^arg(:amount))) + end end identities do @@ -92,6 +97,7 @@ defmodule AshPostgres.Test.Post do code_interface do define_for(AshPostgres.Test.Api) define(:get_by_id, action: :read, get_by: [:id]) + define(:increment_score, args: [{:optional, :amount}]) end relationships do @@ -144,6 +150,7 @@ defmodule AshPostgres.Test.Post do end calculations do + calculate(:score_after_winning, :integer, expr((score || 0) + 1)) calculate(:negative_score, :integer, expr(-score)) calculate(:category_label, :ci_string, expr("(" <> category <> ")")) calculate(:score_with_score, :string, expr(score <> score)) From eabb745fc391ac1d75840953a78867cc2a44ddad Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 11:47:15 -0400 Subject: [PATCH 0013/1215] chore: update tests for latest ash atomics support --- mix.exs | 2 +- mix.lock | 2 +- test/atomics_test.exs | 6 +++--- test/support/resources/post.ex | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 15fda552..03c787a1 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version(github: "ash-project/ash")}, + {:ash, ash_version("~> 2.14 and >= 2.14.5")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 1abc0408..3daa0271 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "18cb24e7f720282d46ad6aed7ba065d9cd71604c", []}, + "ash": {:hex, :ash, "2.14.5", "fee7e6962084be0929ffdbd56ea24f7a6a12e36d385b5cd53ef627cca2aa0269", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "023e69a4d5dc95fd94fa873d98ea1ddcb413d21a7f2de8a50c8ac8b476b0abdb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 2f8b76d5..e2eb7bc1 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -13,7 +13,7 @@ defmodule AshPostgres.AtomicsTest do assert %{price: 2} = post |> Ash.Changeset.for_update(:update, %{}) - |> Ash.Changeset.atomic(:price, expr(price + 1)) + |> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Api.update!() end @@ -26,7 +26,7 @@ defmodule AshPostgres.AtomicsTest do assert_raise Ash.Error.Invalid, ~r/does not exist/, fn -> post |> Ash.Changeset.for_update(:update, %{}) - |> Ash.Changeset.atomic(:organization_id, Ash.UUID.generate()) + |> Ash.Changeset.atomic_update(:organization_id, Ash.UUID.generate()) |> Api.update!() end end @@ -40,7 +40,7 @@ defmodule AshPostgres.AtomicsTest do post = post |> Ash.Changeset.for_update(:update, %{}) - |> Ash.Changeset.atomic(:score, expr(score_after_winning)) + |> Ash.Changeset.atomic_update(:score, expr(score_after_winning)) |> Api.update!() assert post.score == 1 diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6ab5a364..d39951be 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -64,7 +64,7 @@ defmodule AshPostgres.Test.Post do update :increment_score do argument(:amount, :integer, default: 1) - set(:score, expr((score || 0) + ^arg(:amount))) + change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) end end From 9ae51b6c06ddfd394147c3bf5e03747c029ba5f8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 11:48:21 -0400 Subject: [PATCH 0014/1215] chore: release version v1.3.44 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2445cd..da13591c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.44](https://github.com/ash-project/ash_postgres/compare/v1.3.43...v1.3.44) (2023-08-31) + + + + +### Bug Fixes: + +* properly handle ensure nsted calls to `get_path` are jsonb + +### Improvements: + +* support atomics (#165) + ## [v1.3.43](https://github.com/ash-project/ash_postgres/compare/v1.3.42...v1.3.43) (2023-08-22) diff --git a/mix.exs b/mix.exs index 03c787a1..394aeb50 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.43" + @version "1.3.44" def project do [ From 9f77304b22485dbe73786d6cc73959ebb4ef992d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 13:04:08 -0400 Subject: [PATCH 0015/1215] fix: don't clobber loaded data on update --- lib/data_layer.ex | 7 +++++++ test/calculation_test.exs | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 7510610b..aaf8805a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1993,6 +1993,13 @@ defmodule AshPostgres.DataLayer do )} {1, [record]} -> + record = + changeset.resource + |> Ash.Resource.Info.attributes() + |> Enum.reduce(changeset.data, fn attribute, data -> + Map.put(data, attribute.name, Map.get(record, attribute.name)) + end) + maybe_update_tenant(resource, changeset, record) {:ok, record} diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 7c7c38e3..c8416323 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -519,8 +519,6 @@ defmodule AshPostgres.CalculationTest do end test "nested get_path works" do - Logger.configure(level: :debug) - assert "thing" = Post |> Ash.Changeset.new(%{title: "match", price: 10_024, stuff: %{foo: %{bar: "thing"}}}) From 8c60723de2f94cacf6150ecb37c051d3aa8f9a1f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 13:04:34 -0400 Subject: [PATCH 0016/1215] chore: release version v1.3.45 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da13591c..9a01ae3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.45](https://github.com/ash-project/ash_postgres/compare/v1.3.44...v1.3.45) (2023-08-31) + + + + +### Bug Fixes: + +* don't clobber loaded data on update + ## [v1.3.44](https://github.com/ash-project/ash_postgres/compare/v1.3.43...v1.3.44) (2023-08-31) diff --git a/mix.exs b/mix.exs index 394aeb50..72a49132 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.44" + @version "1.3.45" def project do [ From 1359068a6906af208102f83be7ba96ceff4b37eb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 16:03:23 -0400 Subject: [PATCH 0017/1215] fix: use provided values for updates --- lib/data_layer.ex | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index aaf8805a..7af89051 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1870,17 +1870,14 @@ defmodule AshPostgres.DataLayer do |> ecto_changeset(changeset, :update) try do - attr_names = - resource - |> Ash.Resource.Info.attributes() - |> Enum.map(& &1.name) - query = from(row in resource, as: ^0) + select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) + query = query |> default_bindings(resource, changeset.context) - |> Ecto.Query.select(^attr_names) + |> Ecto.Query.select(^select) query = Enum.reduce(ecto_changeset.filters, query, fn {key, value}, query -> @@ -1889,14 +1886,9 @@ defmodule AshPostgres.DataLayer do ) end) - set = - ecto_changeset.changes - |> Map.take(attr_names) - |> Map.to_list() - atomics_result = - Enum.reduce_while(changeset.atomics, {:ok, query, set}, fn {field, expr}, - {:ok, query, set} -> + Enum.reduce_while(changeset.atomics, {:ok, query, []}, fn {field, expr}, + {:ok, query, set} -> used_calculations = Ash.Filter.used_calculations( expr, @@ -1935,9 +1927,16 @@ defmodule AshPostgres.DataLayer do end) case atomics_result do - {:ok, query, set} -> + {:ok, query, dynamics} -> + {params, set, count} = + ecto_changeset.changes + |> Map.to_list() + |> Enum.reduce({[], [], 0}, fn {key, value}, {params, set, count} -> + {[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} + end) + {params, set, _} = - Enum.reduce(set, {[], [], 0}, fn {key, value}, {params, set, count} -> + Enum.reduce(dynamics, {params, set, count}, fn {key, value}, {params, set, count} -> case AshPostgres.Expr.dynamic_expr(query, value, query.__ash_bindings__) do %Ecto.Query.DynamicExpr{} = dynamic -> result = @@ -1992,13 +1991,11 @@ defmodule AshPostgres.DataLayer do filters: ecto_changeset.filters )} - {1, [record]} -> + {1, [result]} -> record = - changeset.resource - |> Ash.Resource.Info.attributes() - |> Enum.reduce(changeset.data, fn attribute, data -> - Map.put(data, attribute.name, Map.get(record, attribute.name)) - end) + changeset.data + |> Map.merge(changeset.attributes) + |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) maybe_update_tenant(resource, changeset, record) From 928e67b96e79f551669af906fe5c9b94659c4533 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 16:03:40 -0400 Subject: [PATCH 0018/1215] chore: release version v1.3.46 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a01ae3e..d7be12ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.46](https://github.com/ash-project/ash_postgres/compare/v1.3.45...v1.3.46) (2023-08-31) + + + + +### Bug Fixes: + +* use provided values for updates + ## [v1.3.45](https://github.com/ash-project/ash_postgres/compare/v1.3.44...v1.3.45) (2023-08-31) diff --git a/mix.exs b/mix.exs index 72a49132..6a8d5cb8 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.45" + @version "1.3.46" def project do [ From 3f4f8c1d32940fa8f4be76033c2ad6c52825a725 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 16:11:29 -0400 Subject: [PATCH 0019/1215] fix: ensure we always select at least one field, and change one field --- lib/data_layer.ex | 76 +++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 7af89051..356b82cc 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1961,45 +1961,51 @@ defmodule AshPostgres.DataLayer do end end) - query = - Map.put(query, :updates, [ - %Ecto.Query.QueryExpr{ - # why do I have to reverse the `set`??? - # it breaks if I don't - expr: [set: Enum.reverse(set)], - params: Enum.reverse(params) - } - ]) - - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - - repo_opts = - Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - - result = - dynamic_repo(resource, changeset).update_all( - query, - [], - repo_opts - ) + case set do + [] -> + {:ok, changeset.data} + + set -> + query = + Map.put(query, :updates, [ + %Ecto.Query.QueryExpr{ + # why do I have to reverse the `set`??? + # it breaks if I don't + expr: [set: Enum.reverse(set)], + params: Enum.reverse(params) + } + ]) + + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + + repo_opts = + Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + + result = + dynamic_repo(resource, changeset).update_all( + query, + [], + repo_opts + ) - case result do - {0, []} -> - {:error, - Ash.Error.Changes.StaleRecord.exception( - resource: resource, - filters: ecto_changeset.filters - )} + case result do + {0, []} -> + {:error, + Ash.Error.Changes.StaleRecord.exception( + resource: resource, + filters: ecto_changeset.filters + )} - {1, [result]} -> - record = - changeset.data - |> Map.merge(changeset.attributes) - |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) + {1, [result]} -> + record = + changeset.data + |> Map.merge(changeset.attributes) + |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) - maybe_update_tenant(resource, changeset, record) + maybe_update_tenant(resource, changeset, record) - {:ok, record} + {:ok, record} + end end {:error, error} -> From db5a5cf4c6a7f02bc0e5c71bf33ab083e872bc76 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Aug 2023 16:11:50 -0400 Subject: [PATCH 0020/1215] chore: release version v1.3.47 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7be12ae..44fe982d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.47](https://github.com/ash-project/ash_postgres/compare/v1.3.46...v1.3.47) (2023-08-31) + + + + +### Bug Fixes: + +* ensure we always select at least one field, and change one field + ## [v1.3.46](https://github.com/ash-project/ash_postgres/compare/v1.3.45...v1.3.46) (2023-08-31) diff --git a/mix.exs b/mix.exs index 6a8d5cb8..27bce9d5 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.46" + @version "1.3.47" def project do [ From 33eb4f9fb75645befa2d9aa25ccf1c718d0b00da Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Sep 2023 11:25:03 -0400 Subject: [PATCH 0021/1215] improvement: better error message for missing table config --- .../ensure_table_or_polymorphic.ex | 18 +++++++++++++++++- mix.lock | 4 ++-- test/support/resources/post.ex | 9 ++++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/transformers/ensure_table_or_polymorphic.ex b/lib/transformers/ensure_table_or_polymorphic.ex index 8270bcf9..13b03c95 100644 --- a/lib/transformers/ensure_table_or_polymorphic.ex +++ b/lib/transformers/ensure_table_or_polymorphic.ex @@ -8,7 +8,23 @@ defmodule AshPostgres.Transformers.EnsureTableOrPolymorphic do Transformer.get_option(dsl, [:postgres], :table) do {:ok, dsl} else - {:error, "Non-polymorphic resources must have a postgres table configured."} + resource = Transformer.get_persisted(dsl, :module) + + raise Spark.Error.DslError, + module: resource, + message: """ + Must configure a table for #{inspect(resource)}. + + For example: + + ```elixir + postgres do + table "the_table" + repo YourApp.Repo + end + ``` + """, + path: [:postgres, :table] end end end diff --git a/mix.lock b/mix.lock index 3daa0271..d4f87f73 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.5", "fee7e6962084be0929ffdbd56ea24f7a6a12e36d385b5cd53ef627cca2aa0269", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "023e69a4d5dc95fd94fa873d98ea1ddcb413d21a7f2de8a50c8ac8b476b0abdb"}, + "ash": {:hex, :ash, "2.14.6", "d70fc1395cb61ade00e12267d69db45cd6f4266df11ef24cea209c388029ee10", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bce08f059b523e7251e06d85260cf7d727386d45993e5120da35630c1fa79f12"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -34,7 +34,7 @@ "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.13.0", "c6ecc96ee3ae0e042e9082a9550a1989ea40182492dc29024a8d9d2b136e5014", [:mix], [], "hexpm", "d0a819491061cd26bfa4450d1c84301a410c19c1782a6577ce15853fc0e7e4e1"}, - "spark": {:hex, :spark, "1.1.22", "68ba00f9acb4c8bc2c93ef82249493687ddf0f0a4f7e79c3c0e22b06719add56", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b798b95990eed8f2409df47b818b5dbcd00e9b5c30d0355465d0b04bbf9b5c4c"}, + "spark": {:hex, :spark, "1.1.25", "9a4836520b71a485f5dedfdab1909071c01375b0409eb4cc2e9cfa8cc28c0398", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "506dfb6e8a851362ed5bb056644f9f852ec7513df77c65c09ef8f71518a91640"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d39951be..eed629d1 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -164,12 +164,19 @@ defmodule AshPostgres.Test.Post do }) ) + calculate( + :count_of_comments_called_baz, + :integer, + expr(count(comments, query: [filter: expr(title == "baz")])) + ) + calculate( :agg_map, :map, expr(%{ called_foo: count(comments, query: [filter: expr(title == "foo")]), - called_bar: count(comments, query: [filter: expr(title == "bar")]) + called_bar: count(comments, query: [filter: expr(title == "bar")]), + called_baz: count_of_comments_called_baz }) ) From 0df8568e8f1f9522843497962a719d36f34b044e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Sep 2023 11:25:53 -0400 Subject: [PATCH 0022/1215] chore: release version v1.3.48 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44fe982d..e6224e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.48](https://github.com/ash-project/ash_postgres/compare/v1.3.47...v1.3.48) (2023-09-04) + + + + +### Improvements: + +* better error message for missing table config + ## [v1.3.47](https://github.com/ash-project/ash_postgres/compare/v1.3.46...v1.3.47) (2023-08-31) diff --git a/mix.exs b/mix.exs index 27bce9d5..b9c584d4 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.47" + @version "1.3.48" def project do [ From 5182a1c6d03ccd88119a9872596904b069e9309e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Sep 2023 13:08:14 -0400 Subject: [PATCH 0023/1215] improvement: implement ash lifecycle tasks --- lib/data_layer.ex | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 356b82cc..573a8416 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -440,6 +440,28 @@ defmodule AshPostgres.DataLayer do AshPostgres.Transformers.PreventMultidimensionalArrayAggregates ] + def migrate(args) do + # TODO: take args that we care about + Mix.Task.run("ash_postgres.migrate", args) + end + + def codegen(args) do + # TODO: take args that we care about + Mix.Task.run("ash_postgres.generate_migrations", args) + end + + def setup(args) do + # TODO: take args that we care about + Mix.Task.run("ash_postgres.create", args) + Mix.Task.run("ash_postgres.migrate", args) + Mix.Task.run("ash_postgres.migrate", ["--tenant" | args]) + end + + def tear_down(args) do + # TODO: take args that we care about + Mix.Task.run("ash_postgres.drop", args) + end + @doc false def tenant_template(value) do value = List.wrap(value) From 6b00276efada3c3ceba7beeaab343b560612c7b3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Sep 2023 13:09:43 -0400 Subject: [PATCH 0024/1215] chore: release version v1.3.49 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6224e74..4a30ccf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.49](https://github.com/ash-project/ash_postgres/compare/v1.3.48...v1.3.49) (2023-09-04) + + + + +### Improvements: + +* implement ash lifecycle tasks + ## [v1.3.48](https://github.com/ash-project/ash_postgres/compare/v1.3.47...v1.3.48) (2023-09-04) diff --git a/mix.exs b/mix.exs index b9c584d4..4f571da1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.48" + @version "1.3.49" def project do [ From 6cc88c82b4a2d5cd437679ca59a091ea18e8332e Mon Sep 17 00:00:00 2001 From: James Harton <59449+jimsynz@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:18:57 +1200 Subject: [PATCH 0025/1215] improvement: Allow resources to opt out of the primary key requirement. (#166) --- lib/data_layer.ex | 17 +++++-- mix.exs | 4 +- mix.lock | 2 +- .../test_repo/post_views/20230905050351.json | 49 +++++++++++++++++++ .../20230905050351_add_post_views.exs | 21 ++++++++ test/primary_key_test.exs | 43 ++++++++++++++-- test/support/registry.ex | 1 + test/support/resources/post.ex | 2 + test/support/resources/post_views.ex | 33 +++++++++++++ 9 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/post_views/20230905050351.json create mode 100644 priv/test_repo/migrations/20230905050351_add_post_views.exs create mode 100644 test/support/resources/post_views.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 573a8416..10907c9d 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -540,8 +540,13 @@ defmodule AshPostgres.DataLayer do def can?(_, :create), do: true def can?(_, :select), do: true def can?(_, :read), do: true - def can?(_, :update), do: true - def can?(_, :destroy), do: true + + def can?(resource, action) when action in ~w[update destroy]a do + resource + |> Ash.Resource.Info.primary_key() + |> Enum.any?() + end + def can?(_, :filter), do: true def can?(_, :limit), do: true def can?(_, :offset), do: true @@ -1754,9 +1759,11 @@ defmodule AshPostgres.DataLayer do value -> List.wrap(value) end - names = [ - {Ash.Resource.Info.primary_key(resource), table(resource, ash_changeset) <> "_pkey"} | names - ] + names = + case Ash.Resource.Info.primary_key(resource) do + [] -> names + fields -> [{fields, table(resource, ash_changeset) <> "_pkey"} | names] + end Enum.reduce(names, changeset, fn {keys, name}, changeset -> diff --git a/mix.exs b/mix.exs index 4f571da1..3ac88048 100644 --- a/mix.exs +++ b/mix.exs @@ -23,6 +23,7 @@ defmodule AshPostgres.MixProject do "coveralls.github": :test, "test.create": :test, "test.migrate": :test, + "test.rollback": :test, "test.migrate_tenants": :test, "test.check_migrations": :test, "test.drop": :test, @@ -161,7 +162,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.14 and >= 2.14.5")}, + {:ash, ash_version("~> 2.14 and >= 2.14.12")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, @@ -202,6 +203,7 @@ defmodule AshPostgres.MixProject do "test.check_migrations": "ash_postgres.generate_migrations --check", "test.migrate_tenants": "ash_postgres.migrate --tenants", "test.migrate": "ash_postgres.migrate", + "test.rollback": "ash_postgres.rollback", "test.create": "ash_postgres.create", "test.reset": ["test.drop", "test.create", "test.migrate", "ash_postgres.migrate --tenants"], "test.drop": "ash_postgres.drop" diff --git a/mix.lock b/mix.lock index d4f87f73..c38741f3 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.6", "d70fc1395cb61ade00e12267d69db45cd6f4266df11ef24cea209c388029ee10", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bce08f059b523e7251e06d85260cf7d727386d45993e5120da35630c1fa79f12"}, + "ash": {:hex, :ash, "2.14.12", "bb2e3dbe82b49407f07854560ba3174a2c479508da8f33eb296764c6445477fb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "799edb6326bd539ede0582b3e62f015a74062d3291e7b2c21ce4b0a2f9ebe88c"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/priv/resource_snapshots/test_repo/post_views/20230905050351.json b/priv/resource_snapshots/test_repo/post_views/20230905050351.json new file mode 100644 index 00000000..4794fbf1 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_views/20230905050351.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "time", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "browser", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + } + ], + "table": "post_views", + "hash": "45E84815BD6E5B9617B491C57A429F210D98AE37428AC7C210B2E9D6AEA27DB3", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20230905050351_add_post_views.exs b/priv/test_repo/migrations/20230905050351_add_post_views.exs new file mode 100644 index 00000000..b09ea4af --- /dev/null +++ b/priv/test_repo/migrations/20230905050351_add_post_views.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.AddPostViews 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 + create table(:post_views, primary_key: false) do + add :time, :utc_datetime_usec, null: false, default: fragment("now()") + add :browser, :text + add :post_id, :uuid, null: false + end + end + + def down do + drop table(:post_views) + end +end \ No newline at end of file diff --git a/test/primary_key_test.exs b/test/primary_key_test.exs index 0b8dbbd6..7f5ba714 100644 --- a/test/primary_key_test.exs +++ b/test/primary_key_test.exs @@ -1,14 +1,51 @@ defmodule AshPostgres.Test.PrimaryKeyTest do + @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, IntegerPost, Post} + alias AshPostgres.Test.{Api, IntegerPost, Post, PostView} require Ash.Query - test "creates resource with integer primary key" do + test "creates record with integer primary key" do assert %IntegerPost{} = IntegerPost |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() end - test "creates resource with uuid primary key" do + test "creates record with uuid primary key" do assert %Post{} = Post |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() end + + describe "resources without a primary key" do + test "records can be created" do + post = + Post + |> Ash.Changeset.for_action(:create, %{title: "not very interesting"}) + |> Api.create!() + + assert {:ok, view} = + PostView + |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id}) + |> Api.create() + + assert view.browser == :firefox + assert view.post_id == post.id + assert DateTime.diff(DateTime.utc_now(), view.time, :microsecond) < 1_000_000 + end + + test "records can be queried" do + post = + Post + |> Ash.Changeset.for_action(:create, %{title: "not very interesting"}) + |> Api.create!() + + expected = + PostView + |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id}) + |> Api.create!() + + assert {:ok, [actual]} = Api.read(PostView) + + assert actual.time == expected.time + assert actual.browser == expected.browser + assert actual.post_id == expected.post_id + end + end end diff --git a/test/support/registry.ex b/test/support/registry.ex index b06ff729..fe0544c4 100644 --- a/test/support/registry.ex +++ b/test/support/registry.ex @@ -8,6 +8,7 @@ defmodule AshPostgres.Test.Registry do entry(AshPostgres.Test.IntegerPost) entry(AshPostgres.Test.Rating) entry(AshPostgres.Test.PostLink) + entry(AshPostgres.Test.PostView) entry(AshPostgres.Test.Author) entry(AshPostgres.Test.Profile) entry(AshPostgres.Test.User) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index eed629d1..a6edfb5c 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -143,6 +143,8 @@ defmodule AshPostgres.Test.Post do source_attribute_on_join_resource: :source_post_id, destination_attribute_on_join_resource: :destination_post_id ) + + has_many(:views, AshPostgres.Test.PostView) end validations do diff --git a/test/support/resources/post_views.ex b/test/support/resources/post_views.ex new file mode 100644 index 00000000..31bedeff --- /dev/null +++ b/test/support/resources/post_views.ex @@ -0,0 +1,33 @@ +defmodule AshPostgres.Test.PostView do + @moduledoc false + use Ash.Resource, data_layer: AshPostgres.DataLayer + + actions do + defaults([:create, :read]) + end + + attributes do + create_timestamp(:time) + attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]]) + end + + relationships do + belongs_to :post, AshPostgres.Test.Post do + allow_nil?(false) + attribute_writable?(true) + end + end + + resource do + require_primary_key?(false) + end + + postgres do + table "post_views" + repo AshPostgres.TestRepo + + references do + reference :post, ignore?: true + end + end +end From 85ac0706b88afa475ac4901746d919d9ba5226f4 Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 6 Sep 2023 17:37:59 +1200 Subject: [PATCH 0026/1215] chore: release version v1.3.50 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a30ccf1..0e95f5b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.50](https://github.com/ash-project/ash_postgres/compare/v1.3.49...v1.3.50) (2023-09-06) + + + + +### Improvements: + +* Allow resources to opt out of the primary key requirement. (#166) + ## [v1.3.49](https://github.com/ash-project/ash_postgres/compare/v1.3.48...v1.3.49) (2023-09-04) diff --git a/mix.exs b/mix.exs index 3ac88048..edf11e61 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.49" + @version "1.3.50" def project do [ From e8a4b41758abb375cb97080115b74e3a69b1b145 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 11 Sep 2023 22:34:51 -0400 Subject: [PATCH 0027/1215] improvement: support vector types and `vector_cosine_distance` --- lib/data_layer.ex | 14 ++++- lib/expr.ex | 19 ++++++- lib/extensions/vector.ex | 51 +++++++++++++++++++ lib/functions/vector_cosine_distance.ex | 9 ++++ .../migration_generator.ex | 14 +++++ lib/repo.ex | 5 +- mix.exs | 2 +- mix.lock | 4 +- 8 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 lib/extensions/vector.ex create mode 100644 lib/functions/vector_cosine_distance.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 10907c9d..365be2ba 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -747,10 +747,20 @@ defmodule AshPostgres.DataLayer do AshPostgres.Functions.ILike ] - if "pg_trgm" in (config[:installed_extensions] || []) do + functions = + if "pg_trgm" in (config[:installed_extensions] || []) do + functions ++ + [ + AshPostgres.Functions.TrigramSimilarity + ] + else + functions + end + + if "vector" in (config[:installed_extensions] || []) do functions ++ [ - AshPostgres.Functions.TrigramSimilarity + AshPostgres.Functions.VectorCosineDistance ] else functions diff --git a/lib/expr.ex b/lib/expr.ex index d1c4952b..7a0eb8c9 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -22,7 +22,7 @@ defmodule AshPostgres.Expr do Type } - alias AshPostgres.Functions.{Fragment, ILike, Like, TrigramSimilarity} + alias AshPostgres.Functions.{Fragment, ILike, Like, TrigramSimilarity, VectorCosineDistance} require Ecto.Query @@ -67,6 +67,19 @@ defmodule AshPostgres.Expr do Ecto.Query.dynamic(fragment("similarity(?, ?)", ^arg1, ^arg2)) end + defp do_dynamic_expr( + query, + %VectorCosineDistance{arguments: [arg1, arg2], embedded?: pred_embedded?}, + bindings, + embedded?, + _type + ) do + arg1 = do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string) + arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string) + + Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)) + end + defp do_dynamic_expr( query, %Like{arguments: [arg1, arg2], embedded?: pred_embedded?}, @@ -1280,6 +1293,10 @@ defmodule AshPostgres.Expr do end end + defp do_dynamic_expr(_query, %Ash.Vector{} = value, _bindings, _embedded?, _type) do + value + end + defp do_dynamic_expr(query, value, bindings, embedded?, _type) when is_map(value) and not is_struct(value) do Map.new(value, fn {key, value} -> diff --git a/lib/extensions/vector.ex b/lib/extensions/vector.ex new file mode 100644 index 00000000..1d6edb80 --- /dev/null +++ b/lib/extensions/vector.ex @@ -0,0 +1,51 @@ +defmodule AshPostgres.Extensions.Vector do + @moduledoc """ + An extension that adds support for the `vector` type. + + Create a file with these contents, not inside of a module: + + ```elixir + Postgrex.Types.define(.PostgrexTypes, [AshPostgres.Extensions.Vector] ++ Ecto.Adapters.Postgres.extensions(), []) + ``` + + And then ensure that you refer to these types in your repo configuration, i.e + + ```elixir + config :my_app, YourApp.Repo, + types: .PostgrexTypes + ``` + """ + import Postgrex.BinaryUtils, warn: false + + def init(opts), do: Keyword.get(opts, :decode_binary, :copy) + + def matching(_), do: [type: "vector"] + + def format(_), do: :binary + + def encode(_) do + quote do + vec when is_struct(vec, Ash.Vector) -> + data = vec |> Ash.Vector.to_binary() + [<> | data] + + vec -> + data = vec |> Ash.Vector.new() |> Ash.Vector.to_binary() + [<> | data] + end + end + + def decode(:copy) do + quote do + <> -> + bin |> :binary.copy() |> Ash.Vector.from_binary() + end + end + + def decode(_) do + quote do + <> -> + bin |> Ash.Vector.from_binary() + end + end +end diff --git a/lib/functions/vector_cosine_distance.ex b/lib/functions/vector_cosine_distance.ex new file mode 100644 index 00000000..f2e20d3a --- /dev/null +++ b/lib/functions/vector_cosine_distance.ex @@ -0,0 +1,9 @@ +defmodule AshPostgres.Functions.VectorCosineDistance do + @moduledoc """ + Maps to the vector cosine distance operator. Requires `vector` extension to be installed. + """ + + use Ash.Query.Function, name: :vector_cosine_distance + + def args, do: [[:vector, :vector]] +end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 94ab7351..2fc59c63 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2624,6 +2624,9 @@ defmodule AshPostgres.MigrationGenerator do {:binary, size} -> {:binary, size} + {other, size} when is_atom(other) and is_integer(size) -> + {other, size} + other -> {other, nil} end @@ -2887,6 +2890,10 @@ defmodule AshPostgres.MigrationGenerator do ["binary", size] end + defp sanitize_type(type, size) when is_atom(type) and is_integer(size) do + [sanitize_type(type, nil), size] + end + defp sanitize_type(type, _) do type end @@ -2976,6 +2983,9 @@ defmodule AshPostgres.MigrationGenerator do {:binary, size} -> {:binary, size} + {other, size} when is_atom(other) and is_integer(size) -> + {other, size} + other -> {other, nil} end @@ -3068,6 +3078,10 @@ defmodule AshPostgres.MigrationGenerator do {:binary, size} end + defp load_type([string, size]) when is_binary(string) and is_integer(size) do + {String.to_existing_atom(string), size} + end + defp load_type(type) do String.to_atom(type) end diff --git a/lib/repo.ex b/lib/repo.ex index f8fdf396..dd11d175 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -17,12 +17,13 @@ defmodule AshPostgres.Repo do * "ash-functions" - This isn't really an extension, but it expresses that certain functions should be added when generating migrations, to support the `||` and `&&` operators in expressions. * `"uuid-ossp"` - Sets UUID primary keys defaults in the migration generator - * `"pg_trgm"` - Makes the `AshPostgres.Predicates.Trigram` custom predicate available + * `"pg_trgm"` - Makes the `AshPostgres.Functions.TrigramSimilarity` function available * "citext" - Allows case insensitive fields to be used + * `"vector"` - Makes the `AshPostgres.Functions.VectorCosineDistance` function available. See `AshPostgres.Extensions.Vector` for more setup instructions. ``` def installed_extensions() do - ["pg_trgm", "uuid-ossp"] + ["pg_trgm", "uuid-ossp", "vector"] end ``` diff --git a/mix.exs b/mix.exs index edf11e61..6442aec7 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.14 and >= 2.14.12")}, + {:ash, ash_version("~> 2.14 and >= 2.14.13")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index c38741f3..12a036f7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.12", "bb2e3dbe82b49407f07854560ba3174a2c479508da8f33eb296764c6445477fb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "799edb6326bd539ede0582b3e62f015a74062d3291e7b2c21ce4b0a2f9ebe88c"}, + "ash": {:hex, :ash, "2.14.13", "f0a33a02e0f5f9e16acfec59547932597024692ed20b550c0109dc9b8cd83c4e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c623900f2d66388c8144de150a551408424a7cd42f7a23025d49bc3013da38c9"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -34,7 +34,7 @@ "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.13.0", "c6ecc96ee3ae0e042e9082a9550a1989ea40182492dc29024a8d9d2b136e5014", [:mix], [], "hexpm", "d0a819491061cd26bfa4450d1c84301a410c19c1782a6577ce15853fc0e7e4e1"}, - "spark": {:hex, :spark, "1.1.25", "9a4836520b71a485f5dedfdab1909071c01375b0409eb4cc2e9cfa8cc28c0398", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "506dfb6e8a851362ed5bb056644f9f852ec7513df77c65c09ef8f71518a91640"}, + "spark": {:hex, :spark, "1.1.26", "109ddd195722f5c50bd24c359f6b2bfbee1a4710b1b9f087f35ccabd47ed6372", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "506e8d4d718fc7bababae81cc47dcc3815414638cd30f5761a0c49d2ec283183"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, From b35f8c715bb4bc8cdc690b58e7ad532d69e66ec8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Sep 2023 16:41:32 -0400 Subject: [PATCH 0028/1215] improvement: add AshPostgres.Tsquery --- lib/types/tsquery.ex | 12 ++++++++++++ lib/types/types.ex | 4 ++++ mix.exs | 2 +- mix.lock | 10 +++++----- test/calculation_test.exs | 18 ++++++++++++++++++ test/support/resources/post.ex | 10 ++++++++++ 6 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 lib/types/tsquery.ex diff --git a/lib/types/tsquery.ex b/lib/types/tsquery.ex new file mode 100644 index 00000000..9115b0e1 --- /dev/null +++ b/lib/types/tsquery.ex @@ -0,0 +1,12 @@ +defmodule AshPostgres.Tsquery do + @moduledoc """ + A thin wrapper around `:string` for working with tsquery types in calculations. + + A calculation of this type cannot be selected, but may be used in calculations. + """ + + use Ash.Type.NewType, subtype_of: :term + + @impl true + def storage_type(_), do: :tsquery +end diff --git a/lib/types/types.ex b/lib/types/types.ex index 87200426..220ae1e1 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -29,6 +29,10 @@ defmodule AshPostgres.Types do parameterized_type(Ash.Type.StringWrapper, constraints) end + def parameterized_type(:tsquery, constraints) do + parameterized_type(AshPostgres.Tsquery, constraints) + end + def parameterized_type(type, _constraints) when type in [Ash.Type.Map, Ash.Type.Map.EctoType], do: nil diff --git a/mix.exs b/mix.exs index 6442aec7..c29634a9 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.14 and >= 2.14.13")}, + {:ash, ash_version("~> 2.14 and >= 2.14.18")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 12a036f7..90c0fe6a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.13", "f0a33a02e0f5f9e16acfec59547932597024692ed20b550c0109dc9b8cd83c4e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c623900f2d66388c8144de150a551408424a7cd42f7a23025d49bc3013da38c9"}, + "ash": {:hex, :ash, "2.14.18", "ac2fd2f274f4989d3c71de3df9a603941bc47ac6c8d27006df78f78844114969", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ec44ad258eb71a2dd5210f67bd882698ea112f6dad79505b156594be06e320e5"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -12,7 +12,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, + "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, "ex_doc": {:hex, :ex_doc, "0.28.3", "6eea2f69995f5fba94cd6dd398df369fe4e777a47cd887714a0976930615c9e6", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "05387a6a2655b5f9820f3f627450ed20b4325c25977b2ee69bed90af6688e718"}, "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, @@ -33,10 +33,10 @@ "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, - "sourceror": {:hex, :sourceror, "0.13.0", "c6ecc96ee3ae0e042e9082a9550a1989ea40182492dc29024a8d9d2b136e5014", [:mix], [], "hexpm", "d0a819491061cd26bfa4450d1c84301a410c19c1782a6577ce15853fc0e7e4e1"}, - "spark": {:hex, :spark, "1.1.26", "109ddd195722f5c50bd24c359f6b2bfbee1a4710b1b9f087f35ccabd47ed6372", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "506e8d4d718fc7bababae81cc47dcc3815414638cd30f5761a0c49d2ec283183"}, + "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, + "spark": {:hex, :spark, "1.1.39", "f143b84a5b796bf2d83ec8fb4793ee9e66e67510c40d785f9a67050bb88e7677", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "d71bc26014c7e7abcdcf553f4cf7c5a5ff96f8365b1e20be3768ce503aafb203"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, + "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index c8416323..2e6e81a0 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -3,6 +3,7 @@ defmodule AshPostgres.CalculationTest do alias AshPostgres.Test.{Account, Api, Author, Comment, Post, User} require Ash.Query + import Ash.Expr test "an expression calculation can be filtered on" do post = @@ -392,6 +393,23 @@ defmodule AshPostgres.CalculationTest do end end + test "arguments with cast_in_query?: false are not cast" do + Post + |> Ash.Changeset.new(%{title: "match", score: 42}) + |> Api.create!() + + Post + |> Ash.Changeset.new(%{title: "not", score: 42}) + |> Api.create!() + + assert [post] = + Post + |> Ash.Query.filter(similarity(search: expr(query(search: "match")))) + |> Api.read!() + + assert post.title == "match" + end + describe "string split expression" do test "with the default delimiter" do author = diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index a6edfb5c..e3074e5a 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -186,6 +186,16 @@ defmodule AshPostgres.Test.Post do load: [:count_of_comments, :count_of_linked_posts] ) + calculate :similarity, + :boolean, + expr(fragment("(to_tsvector(?) @@ ?)", title, ^arg(:search))) do + argument(:search, AshPostgres.Tsquery, allow_expr?: true, allow_nil?: false) + end + + calculate :query, AshPostgres.Tsquery, expr(fragment("to_tsquery(?)", ^arg(:search))) do + argument(:search, :string, allow_expr?: true, allow_nil?: false) + end + calculate( :calc_returning_json, AshPostgres.Test.Money, From 81ce903e6153bbcf1887f786d5cadbddd111f494 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Sep 2023 17:28:24 -0400 Subject: [PATCH 0029/1215] improvement: add `AshPostgres.Tsvector` --- lib/types/tsvector.ex | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 lib/types/tsvector.ex diff --git a/lib/types/tsvector.ex b/lib/types/tsvector.ex new file mode 100644 index 00000000..a00705ec --- /dev/null +++ b/lib/types/tsvector.ex @@ -0,0 +1,12 @@ +defmodule AshPostgres.Tsvector do + @moduledoc """ + A thin wrapper around `:string` for working with tsvector types in calculations. + + A calculation of this type cannot be selected, but may be used in calculations. + """ + + use Ash.Type.NewType, subtype_of: :term + + @impl true + def storage_type(_), do: :tsvector +end From 43a56f2d57eff46d4987031faf25da1696f6cdb5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Sep 2023 18:01:09 -0400 Subject: [PATCH 0030/1215] docs: add primer to docs --- documentation/topics/migrations_and_tasks.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/topics/migrations_and_tasks.md b/documentation/topics/migrations_and_tasks.md index f8370f09..3d21e88e 100644 --- a/documentation/topics/migrations_and_tasks.md +++ b/documentation/topics/migrations_and_tasks.md @@ -1,5 +1,9 @@ # Migrations +## Migration Generator Primer + + + ## Tasks The available tasks are: From cad4e414a1e4703133f46897049868a9be14d891 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Sep 2023 18:01:22 -0400 Subject: [PATCH 0031/1215] chore: release version v1.3.51 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e95f5b8..3c5faf63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.51](https://github.com/ash-project/ash_postgres/compare/v1.3.50...v1.3.51) (2023-09-20) + + + + +### Improvements: + +* add `AshPostgres.Tsvector` + +* add AshPostgres.Tsquery + +* support vector types and `vector_cosine_distance` + ## [v1.3.50](https://github.com/ash-project/ash_postgres/compare/v1.3.49...v1.3.50) (2023-09-06) diff --git a/mix.exs b/mix.exs index c29634a9..255fdb03 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.50" + @version "1.3.51" def project do [ From b03fb51c3640f682cff1e88fa449ec3ee33ddb46 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Thu, 21 Sep 2023 22:44:02 +0300 Subject: [PATCH 0032/1215] fix: use `:wrap_list` type instead of custom validaitons (#167) --- lib/custom_index.ex | 2 +- lib/data_layer.ex | 26 ++------------------------ 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 5a5ba8c1..6dc54107 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -19,7 +19,7 @@ defmodule AshPostgres.CustomIndex do @schema [ fields: [ - type: {:list, {:or, [:atom, :string]}}, + type: {:wrap_list, {:or, [:atom, :string]}}, doc: "The fields to include in the index." ], name: [ diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 365be2ba..688f0584 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -15,7 +15,7 @@ defmodule AshPostgres.DataLayer do ], schema: [ template: [ - type: {:custom, __MODULE__, :tenant_template, []}, + type: {:wrap_list, {:or, [:string, :atom]}}, required: true, doc: """ A template that will cause the resource to create/manage the specified schema. @@ -321,7 +321,7 @@ defmodule AshPostgres.DataLayer do """ ], skip_unique_indexes: [ - type: {:custom, __MODULE__, :validate_skip_unique_indexes, []}, + type: {:wrap_list, :atom}, default: false, doc: "Skip generating unique indexes when generating migrations" ], @@ -462,28 +462,6 @@ defmodule AshPostgres.DataLayer do Mix.Task.run("ash_postgres.drop", args) end - @doc false - def tenant_template(value) do - value = List.wrap(value) - - if Enum.all?(value, &(is_binary(&1) || is_atom(&1))) do - {:ok, value} - else - {:error, "Expected all values for `manages_tenant` to be strings or atoms"} - end - end - - @doc false - def validate_skip_unique_indexes(indexes) do - indexes = List.wrap(indexes) - - if Enum.all?(indexes, &is_atom/1) do - {:ok, indexes} - else - {:error, "All indexes to skip must be atoms"} - end - end - import Ecto.Query, only: [from: 2, subquery: 1] @impl true From 49490e6fb489edf6a80248e97db652c31de381f1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Sep 2023 15:42:39 -0400 Subject: [PATCH 0033/1215] improvement: support data_layer_context option on transactions chore: refactor create/upsert to use bulk create --- lib/data_layer.ex | 172 ++++++++++++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 68 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 688f0584..5a829303 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1216,26 +1216,42 @@ defmodule AshPostgres.DataLayer do changesets = Enum.to_list(stream) - ecto_changesets = - changesets - |> Stream.map(& &1.attributes) - |> Enum.to_list() + ecto_changesets = Enum.map(changesets, & &1.attributes) - resource - |> dynamic_repo(resource, Enum.at(changesets, 0)).insert_all(ecto_changesets, opts) + source = + if table = Enum.at(changesets, 0).context[:data_layer][:table] do + {table, resource} + else + resource + end + + repo = dynamic_repo(resource, Enum.at(changesets, 0)) + + source + |> repo.insert_all(ecto_changesets, opts) |> case do {_, nil} -> :ok {_, results} -> - {:ok, - Stream.zip_with(results, changesets, fn result, changeset -> - Ash.Resource.put_metadata( - result, - :bulk_create_index, - changeset.context.bulk_create.index - ) - end)} + if options[:single?] do + Enum.each(results, &maybe_create_tenant!(resource, &1)) + + {:ok, results} + else + {:ok, + Stream.zip_with(results, changesets, fn result, changeset -> + if !opts[:upsert?] do + maybe_create_tenant!(resource, result) + end + + Ash.Resource.put_metadata( + result, + :bulk_create_index, + changeset.context.bulk_create.index + ) + end)} + end end rescue e -> @@ -1244,25 +1260,29 @@ defmodule AshPostgres.DataLayer do handle_raised_error( e, __STACKTRACE__, - {:bulk_create, ecto_changeset(changeset.data, changeset, :create)}, + {:bulk_create, ecto_changeset(changeset.data, changeset, :create, false)}, resource ) end @impl true def create(resource, changeset) do - changeset.data - |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :create) - |> dynamic_repo(resource, changeset).insert( - repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - ) - |> from_ecto() - |> handle_errors() - |> case do - {:ok, result} -> - maybe_create_tenant!(resource, result) + changeset = %{ + changeset + | data: + Map.update!( + changeset.data, + :__meta__, + &Map.put(&1, :source, table(resource, changeset)) + ) + } + case bulk_create(resource, [changeset], %{ + single?: true, + tenant: changeset.tenant, + return_records?: true + }) do + {:ok, [result]} -> {:ok, result} {:error, error} -> @@ -1325,8 +1345,6 @@ defmodule AshPostgres.DataLayer do {:error, Enum.map(errors, &to_ash_error/1)} end - defp handle_errors({:ok, val}), do: {:ok, val} - defp to_ash_error({field, {message, vars}}) do Ash.Error.Changes.InvalidAttribute.exception( field: field, @@ -1335,7 +1353,7 @@ defmodule AshPostgres.DataLayer do ) end - defp ecto_changeset(record, changeset, type) do + defp ecto_changeset(record, changeset, type, table_error? \\ true) do filters = if changeset.action_type == :create do %{} @@ -1367,7 +1385,7 @@ defmodule AshPostgres.DataLayer do ecto_changeset = record |> to_ecto() - |> set_table(changeset, type) + |> set_table(changeset, type, table_error?) |> Ecto.Changeset.change(Map.take(changeset.attributes, attributes_to_change)) |> Map.update!(:filters, &Map.merge(&1, filters)) |> add_configured_foreign_key_constraints(record.__struct__) @@ -1529,7 +1547,7 @@ defmodule AshPostgres.DataLayer do end) end - defp set_table(record, changeset, operation) do + defp set_table(record, changeset, operation, table_error?) do if AshPostgres.DataLayer.Info.polymorphic?(record.__struct__) do table = changeset.context[:data_layer][:table] || @@ -1539,7 +1557,11 @@ defmodule AshPostgres.DataLayer do if table do Ecto.put_meta(record, source: table) else - raise_table_error!(changeset.resource, operation) + if table_error? do + raise_table_error!(changeset.resource, operation) + else + record + end end prefix = @@ -1749,8 +1771,15 @@ defmodule AshPostgres.DataLayer do names = case Ash.Resource.Info.primary_key(resource) do - [] -> names - fields -> [{fields, table(resource, ash_changeset) <> "_pkey"} | names] + [] -> + names + + fields -> + if table = table(resource, ash_changeset) do + [{fields, table <> "_pkey"} | names] + else + [] + end end Enum.reduce(names, changeset, fn @@ -1764,40 +1793,38 @@ defmodule AshPostgres.DataLayer do @impl true def upsert(resource, changeset, keys \\ nil) do - keys = keys || Ash.Resource.Info.primary_key(resource) - - explicitly_changing_attributes = - Enum.map( - Map.keys(changeset.attributes) -- Map.get(changeset, :defaults, []) -- keys, - fn key -> - {key, Ash.Changeset.get_attribute(changeset, key)} - end - ) - - on_conflict = - changeset - |> update_defaults() - |> Keyword.merge(explicitly_changing_attributes) - - conflict_target = conflict_target(resource, keys) - - repo_opts = - changeset.timeout - |> repo_opts(changeset.tenant, changeset.resource) - |> Keyword.put(:on_conflict, set: on_conflict) - |> Keyword.put(:conflict_target, conflict_target) - if AshPostgres.DataLayer.Info.manage_tenant_update?(resource) do {:error, "Cannot currently upsert a resource that owns a tenant"} else - changeset.data - |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :upsert) - |> AshPostgres.DataLayer.Info.repo(resource).insert( - Keyword.put(repo_opts, :returning, true) - ) - |> from_ecto() - |> handle_errors() + keys = keys || Ash.Resource.Info.primary_key(resource) + + explicitly_changing_attributes = + Enum.map( + Map.keys(changeset.attributes) -- Map.get(changeset, :defaults, []) -- keys, + fn key -> + {key, Ash.Changeset.get_attribute(changeset, key)} + end + ) + + on_conflict = + changeset + |> update_defaults() + |> Keyword.merge(explicitly_changing_attributes) + + case bulk_create(resource, [changeset], %{ + single?: true, + upsert?: true, + tenant: changeset.tenant, + upsert_keys: keys, + upsert_fields: Keyword.keys(on_conflict), + return_records?: true + }) do + {:ok, [result]} -> + {:ok, result} + + {:error, error} -> + {:error, error} + end end end @@ -2506,15 +2533,24 @@ defmodule AshPostgres.DataLayer do @impl true def transaction(resource, func, timeout \\ nil, reason \\ %{type: :custom, metadata: %{}}) do + repo = + case reason[:data_layer_context] do + %{repo: repo} when not is_nil(repo) -> + repo + + _ -> + AshPostgres.DataLayer.Info.repo(resource) + end + func = fn -> - AshPostgres.DataLayer.Info.repo(resource).on_transaction_begin(reason) + repo.on_transaction_begin(reason) func.() end if timeout do - AshPostgres.DataLayer.Info.repo(resource).transaction(func, timeout: timeout) + repo.transaction(func, timeout: timeout) else - AshPostgres.DataLayer.Info.repo(resource).transaction(func) + repo.transaction(func) end end From a56f4ef52f953fe3c164d725bbb3f382f04d40b6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Sep 2023 16:14:25 -0400 Subject: [PATCH 0034/1215] docs: overhaul hexdocs --- .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 369 ++++++++++++++++++ documentation/topics/references.md | 23 ++ .../topics/schema-based-multitenancy.md | 2 +- lib/check_constraint.ex | 2 +- lib/custom_index.ex | 2 +- lib/data_layer.ex | 73 +--- lib/reference.ex | 25 +- lib/statement.ex | 9 +- .../ensure_table_or_polymorphic.ex | 2 +- ...event_multidimensional_array_aggregates.ex | 2 +- lib/transformers/validate_references.ex | 2 +- lib/transformers/verify_repo.ex | 2 +- mix.exs | 74 +++- mix.lock | 10 +- 14 files changed, 471 insertions(+), 126 deletions(-) create mode 100644 documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd create mode 100644 documentation/topics/references.md diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd new file mode 100644 index 00000000..4cc4e571 --- /dev/null +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -0,0 +1,369 @@ +# DSL: AshPostgres.DataLayer + +A postgres data layer that leverages Ecto's postgres capabilities. + + +## postgres +Postgres data layer configuration + + +### Nested DSLs + * [custom_indexes](#postgres-custom_indexes) + * index + * [custom_statements](#postgres-custom_statements) + * statement + * [manage_tenant](#postgres-manage_tenant) + * [references](#postgres-references) + * reference + * [check_constraints](#postgres-check_constraints) + * check_constraint + + +### Examples +``` +postgres do + repo MyApp.Repo + table "organizations" +end + +``` + + + + +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `repo`* | `atom` | | The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more | +| `migrate?` | `boolean` | `true` | Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` | +| `migration_types` | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | +| `migration_defaults` | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | +| `base_filter_sql` | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | +| `simple_join_first_aggregates` | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | +| `skip_unique_indexes` | `list(atom) \| atom` | `false` | Skip generating unique indexes when generating migrations | +| `unique_index_names` | `{list(atom), String.t} \| {list(atom), String.t, String.t}` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | +| `exclusion_constraint_names` | ``any`` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | +| `identity_index_names` | ``any`` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | +| `foreign_key_names` | `{atom, String.t} \| {String.t, String.t}` | `[]` | A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` | +| `migration_ignore_attributes` | `list(atom)` | `[]` | A list of attributes that will be ignored when generating migrations. | +| `table` | `String.t` | | The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. | +| `schema` | `String.t` | | The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. | +| `polymorphic?` | `boolean` | `false` | Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. | + + +## postgres.custom_indexes +A section for configuring indexes to be created by the migration generator. + +In general, prefer to use `identities` for simple unique constraints. This is a tool to allow +for declaring more complex indexes. + + +### Nested DSLs + * [index](#postgres-custom_indexes-index) + + +### Examples +``` +custom_indexes do + index [:column1, :column2], unique: true, where: "thing = TRUE" +end + +``` + + + + +## postgres.custom_indexes.index +```elixir +index fields +``` + + +Add an index to be managed by the migration generator. + + + + +### Examples +``` +index ["column", "column2"], unique: true, where: "thing = TRUE" +``` + + + +### Arguments +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `fields` | `list(atom \| String.t) \| atom \| String.t` | | The fields to include in the index. | +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `name` | `String.t` | | the name of the index. Defaults to "#{table}_#{column}_index". | +| `unique` | `boolean` | `false` | indicates whether the index should be unique. | +| `concurrently` | `boolean` | `false` | indicates whether the index should be created/dropped concurrently. | +| `using` | `String.t` | | configures the index type. | +| `prefix` | `String.t` | | specify an optional prefix for the index. | +| `where` | `String.t` | | specify conditions for a partial index. | +| `message` | `String.t` | | A custom message to use for unique indexes that have been violated | +| `include` | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | + + + + + +### Introspection + +Target: `AshPostgres.CustomIndex` + + +## postgres.custom_statements +A section for configuring custom statements to be added to migrations. + +Changing custom statements may require manual intervention, because Ash can't determine what order they should run +in (i.e if they depend on table structure that you've added, or vice versa). As such, any `down` statements we run +for custom statements happen first, and any `up` statements happen last. + +Additionally, when changing a custom statement, we must make some assumptions, i.e that we should migrate +the old structure down using the previously configured `down` and recreate it. + +This may not be desired, and so what you may end up doing is simply modifying the old migration and deleting whatever was +generated by the migration generator. As always: read your migrations after generating them! + + +### Nested DSLs + * [statement](#postgres-custom_statements-statement) + + +### Examples +``` +custom_statements do + # the name is used to detect if you remove or modify the statement + statement :pgweb_idx do + up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));" + down "DROP INDEX pgweb_idx;" + end +end + +``` + + + + +## postgres.custom_statements.statement +```elixir +statement name +``` + + +Add a custom statement for migrations. + + + + +### Examples +``` +statement :pgweb_idx do + up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));" + down "DROP INDEX pgweb_idx;" +end + +``` + + + +### Arguments +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `name`* | `atom` | | The name of the statement, must be unique within the resource | +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `up`* | `String.t` | | How to create the structure of the statement | +| `down`* | `String.t` | | How to tear down the structure of the statement | +| `code?` | `boolean` | `false` | By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations | + + + + + +### Introspection + +Target: `AshPostgres.Statement` + + +## postgres.manage_tenant +Configuration for the behavior of a resource that manages a tenant + + + + +### Examples +``` +manage_tenant do + template ["organization_", :id] + create? true + update? false +end + +``` + + + + +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `template`* | `list(String.t \| atom) \| String.t \| atom` | | A template that will cause the resource to create/manage the specified schema. | +| `create?` | `boolean` | `true` | Whether or not to automatically create a tenant when a record is created | +| `update?` | `boolean` | `true` | Whether or not to automatically update the tenant name if the record is udpated | + + + + +## postgres.references +A section for configuring the references (foreign keys) in resource migrations. + +This section is only relevant if you are using the migration generator with this resource. +Otherwise, it has no effect. + + +### Nested DSLs + * [reference](#postgres-references-reference) + + +### Examples +``` +references do + reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" +end + +``` + + + + +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `polymorphic_on_delete` | `:delete \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | +| `polymorphic_on_update` | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | +| `polymorphic_name` | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | + + + +## postgres.references.reference +```elixir +reference relationship +``` + + +Configures the reference for a relationship in resource migrations. + +Keep in mind that multiple relationships can theoretically involve the same destination and foreign keys. +In those cases, you only need to configure the `reference` behavior for one of them. Any conflicts will result +in an error, across this resource and any other resources that share a table with this one. For this reason, +instead of adding a reference configuration for `:nothing`, its best to just leave the configuration out, as that +is the default behavior if *no* relationship anywhere has configured the behavior of that reference. + + + + +### Examples +``` +reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" +``` + + + +### Arguments +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `relationship`* | `atom` | | The relationship to be configured | +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `ignore?` | `boolean` | | If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way | +| `on_delete` | `:delete \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | +| `on_update` | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | +| `deferrable` | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | +| `name` | `String.t` | | The name of the foreign key to generate in the database. Defaults to __fkey | + + + + + +### Introspection + +Target: `AshPostgres.Reference` + + +## postgres.check_constraints +A section for configuring the check constraints for a given table. + +This can be used to automatically create those check constraints, or just to provide message when they are raised + + +### Nested DSLs + * [check_constraint](#postgres-check_constraints-check_constraint) + + +### Examples +``` +check_constraints do + check_constraint :price, "price_must_be_positive", check: "price > 0", message: "price must be positive" +end + +``` + + + + +## postgres.check_constraints.check_constraint +```elixir +check_constraint attribute, name +``` + + +Add a check constraint to be validated. + +If a check constraint exists on the table but not in this section, and it produces an error, a runtime error will be raised. + +Provide a list of attributes instead of a single attribute to add the message to multiple attributes. + +By adding the `check` option, the migration generator will include it when generating migrations. + + + + +### Examples +``` +check_constraint :price, "price_must_be_positive", check: "price > 0", message: "price must be positive" + +``` + + + +### Arguments +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `attribute`* | ``any`` | | The attribute or list of attributes to which an error will be added if the check constraint fails | +| `name`* | `String.t` | | The name of the constraint | +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `message` | `String.t` | | The message to be added if the check constraint fails | +| `check` | `String.t` | | The contents of the check. If this is set, the migration generator will include it when generating migrations | + + + + + +### Introspection + +Target: `AshPostgres.CheckConstraint` + + + + + + diff --git a/documentation/topics/references.md b/documentation/topics/references.md new file mode 100644 index 00000000..ceecb612 --- /dev/null +++ b/documentation/topics/references.md @@ -0,0 +1,23 @@ +# References + +To configure the foreign keys on a resource, we use the `references` block. + +For example: + +```elixir +references do + reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" +end +``` + +## Important + +No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens *directly* in the database. That + +## Nothing vs Restrict + +The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). `:restrict` will prevent the deletion from happening *before* the end of the database transaction, whereas `:nothing` allows the transaction to complete before doing so. This allows for things like updating or deleting the destination row and *then* updating updating or deleting the reference(as long as you are in a transaction). + +## On Delete + +This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, *not* a `destroy` action in your resource. diff --git a/documentation/topics/schema-based-multitenancy.md b/documentation/topics/schema-based-multitenancy.md index a8424466..fae8e907 100644 --- a/documentation/topics/schema-based-multitenancy.md +++ b/documentation/topics/schema-based-multitenancy.md @@ -31,4 +31,4 @@ end With this configuration, if you create an organization, it will create a corresponding schema, e.g. `org_10` in the database. Then it will run your tenant migrations on that schema. To override the tenant_migrations path, implement the `c:AshPostgres.Repo.tenant_migrations_path/0` callback. -Notice that `manage_tenant` is nested inside the `postgres` block. This is because the method of managing tenants is specific to postgres, and if another data layer supported multitenancy they may or may not support managing tenants in the same way. \ No newline at end of file +Notice that `manage_tenant` is nested inside the `postgres` block. This is because the method of managing tenants is specific to postgres, and if another data layer supported multitenancy they may or may not support managing tenants in the same way. diff --git a/lib/check_constraint.ex b/lib/check_constraint.ex index 2329a5e0..3cfeecd7 100644 --- a/lib/check_constraint.ex +++ b/lib/check_constraint.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.CheckConstraint do - @moduledoc false + @moduledoc "Represents a configured check constraint on the table backing a resource" defstruct [:attribute, :name, :message, :check] diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 6dc54107..03a1b318 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.CustomIndex do - @moduledoc false + @moduledoc "Represents a custom index on the table backing a resource" @fields [ :table, :fields, diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5a829303..b99538bc 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -19,14 +19,6 @@ defmodule AshPostgres.DataLayer do required: true, doc: """ A template that will cause the resource to create/manage the specified schema. - - Use this if you have a resource that, when created, it should create a new tenant - for you. For example, if you have a `customer` resource, and you want to create - a schema for each customer based on their id, e.g `customer_10` set this option - to `["customer_", :id]`. Then, when this is created, it will create a schema called - `["customer_", :id]`, and run your tenant migrations on it. Then, if you were to change - that customer's id to `20`, it would rename the schema to `customer_20`. Generally speaking - you should avoid changing the tenant id. """ ], create?: [ @@ -298,9 +290,7 @@ defmodule AshPostgres.DataLayer do type: :keyword_list, default: [], doc: """ - A keyword list of attribute names to the ecto migration default that should be used for that attribute. Only necessary if you need to override the defaults. - - The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\\\"now()\\\\")`, or for `nil`, use `\\\\"nil\\\\"`. + A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\\\"now()\\\\")`, or for `nil`, use `\\\\"nil\\\\"`. """ ], base_filter_sql: [ @@ -312,12 +302,7 @@ defmodule AshPostgres.DataLayer do type: {:list, :atom}, default: [], doc: """ - A list of `:first` type aggregate names that can be joined to using a simple join. - - This is used in the relatively rare case that you have a `:first` aggregate that uses - a `has_many` or `many_to_many` relationship in its path, but your `filter` statement ensures - that there is only one result. In these cases, we can use a more optimized version of - computing the aggregate value. + A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. """ ], skip_unique_indexes: [ @@ -326,15 +311,10 @@ defmodule AshPostgres.DataLayer do doc: "Skip generating unique indexes when generating migrations" ], unique_index_names: [ - type: :any, + type: {:or, [{:tuple, [{:list, :atom}, :string]}, {:tuple, [{:list, :atom}, :string, :string]}]}, default: [], doc: """ - A list of unique index names that could raise errors, or an mfa to a function that takes a changeset - and returns the list. Must be in the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` - - Note that this is *not* used to rename the unique indexes created from `identities`. - Use `identity_index_names` for that. This is used to tell ash_postgres about unique indexes that - exist in the database that it didn't create. + A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` """ ], exclusion_constraint_names: [ @@ -348,16 +328,14 @@ defmodule AshPostgres.DataLayer do type: :any, default: [], doc: """ - A keyword list of identity names to the unique index name that they should use when being managed by the migration - generator. + A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. """ ], foreign_key_names: [ - type: :any, + type: {:or, [{:tuple, [:atom, :string]}, {:tuple, [:string, :string]}]}, default: [], doc: """ - A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns the list. - Must be in the format `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` + A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` """ ], migration_ignore_attributes: [ @@ -370,38 +348,20 @@ defmodule AshPostgres.DataLayer do table: [ type: :string, doc: """ - The table to store and read the resource from. Required unless `polymorphic?` is true. - - If this is changed, the migration generator will not remove the old table. + The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. """ ], schema: [ type: :string, doc: """ - The schema that the table is located in. - Multitenancy supersedes this, so this acts as the schema in the cases that `global?: true` is set. - If this is changed, the migration generator will not remove the old table in the old schema. + The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. """ ], polymorphic?: [ type: :boolean, default: false, doc: """ - Declares this resource as polymorphic. - - Polymorphic resources cannot be read or updated unless the table is provided in the query/changeset context. - - For example: - - PolymorphicResource - |> Ash.Query.set_context(%{data_layer: %{table: "table"}}) - |> MyApi.read!() - - When relating to polymorphic resources, you'll need to use the `context` option on relationships, - e.g - - belongs_to :polymorphic_association, PolymorphicResource, - context: %{data_layer: %{table: "table"}} + Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. """ ] ] @@ -416,19 +376,6 @@ defmodule AshPostgres.DataLayer do @moduledoc """ A postgres data layer that leverages Ecto's postgres capabilities. - - - - ## DSL Documentation - - ### Index - - #{Spark.Dsl.Extension.doc_index(@sections)} - - ### Docs - - #{Spark.Dsl.Extension.doc(@sections)} - """ use Spark.Dsl.Extension, diff --git a/lib/reference.ex b/lib/reference.ex index f400eb17..c6c164d6 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.Reference do - @moduledoc false + @moduledoc "Represents the configuration of a reference (i.e foreign key)." defstruct [:relationship, :on_delete, :on_update, :name, :deferrable, ignore?: false] def schema do @@ -18,35 +18,12 @@ defmodule AshPostgres.Reference do type: {:one_of, [:delete, :nilify, :nothing, :restrict]}, doc: """ What should happen to records of this resource when the referenced record of the *destination* resource is deleted. - - The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). - `:restrict` will prevent the deletion from happening *before* the end of the database transaction, whereas `:nothing` allows the - transaction to complete before doing so. This allows for things like deleting the destination row and *then* deleting the source - row. - - ## Important! - - No resource logic is applied with this operation! No authorization rules or validations take place, and no notifications are issued. - This operation happens *directly* in the database. - - This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, *not* - a `destroy` action in your resource. """ ], on_update: [ type: {:one_of, [:update, :nilify, :nothing, :restrict]}, doc: """ What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. - - The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). - `:restrict` will prevent the deletion from happening *before* the end of the database transaction, whereas `:nothing` allows the - transaction to complete before doing so. This allows for things like updating the destination row and *then* updating the reference - as long as you are in a transaction. - - ## Important! - - No resource logic is applied with this operation! No authorization rules or validations take place, and no notifications are issued. - This operation happens *directly* in the database. """ ], deferrable: [ diff --git a/lib/statement.ex b/lib/statement.ex index 42fa9c49..ae04496e 100644 --- a/lib/statement.ex +++ b/lib/statement.ex @@ -1,5 +1,6 @@ defmodule AshPostgres.Statement do - @moduledoc false + @moduledoc "Represents a custom statement to be run in generated migrations" + @fields [ :name, :up, @@ -23,11 +24,7 @@ defmodule AshPostgres.Statement do type: :boolean, default: false, doc: """ - Whether the provided up/down should be treated as code or sql strings. - - By default, we place the strings inside of ecto migration's `execute/1` - function and assume they are sql. Use this option if you want to provide custom - elixir code to be placed directly in the migrations + By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations """ ], up: [ diff --git a/lib/transformers/ensure_table_or_polymorphic.ex b/lib/transformers/ensure_table_or_polymorphic.ex index 13b03c95..064d0262 100644 --- a/lib/transformers/ensure_table_or_polymorphic.ex +++ b/lib/transformers/ensure_table_or_polymorphic.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.Transformers.EnsureTableOrPolymorphic do - @moduledoc "Ensures that there is a table configured or the resource is polymorphic" + @moduledoc false use Spark.Dsl.Transformer alias Spark.Dsl.Transformer diff --git a/lib/transformers/prevent_multidimensional_array_aggregates.ex b/lib/transformers/prevent_multidimensional_array_aggregates.ex index 089bc8d1..a52ea723 100644 --- a/lib/transformers/prevent_multidimensional_array_aggregates.ex +++ b/lib/transformers/prevent_multidimensional_array_aggregates.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do - @moduledoc "Prevents at compile time certain aggregates that are unsupported by AshPostgres" + @moduledoc false use Spark.Dsl.Transformer alias Spark.Dsl.Transformer diff --git a/lib/transformers/validate_references.ex b/lib/transformers/validate_references.ex index e20905c7..0a46e6d2 100644 --- a/lib/transformers/validate_references.ex +++ b/lib/transformers/validate_references.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.Transformers.ValidateReferences do - @moduledoc "Ensures that all `references` on a resource refer to a real relationship" + @moduledoc false use Spark.Dsl.Transformer alias Spark.Dsl.Transformer diff --git a/lib/transformers/verify_repo.ex b/lib/transformers/verify_repo.ex index 2a5753eb..89b53a35 100644 --- a/lib/transformers/verify_repo.ex +++ b/lib/transformers/verify_repo.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.Transformers.VerifyRepo do - @moduledoc "Verifies that the repo is configured correctly" + @moduledoc false use Spark.Dsl.Transformer alias Spark.Dsl.Transformer diff --git a/mix.exs b/mix.exs index 255fdb03..6d171cdb 100644 --- a/mix.exs +++ b/mix.exs @@ -64,15 +64,16 @@ defmodule AshPostgres.MixProject do end defp extras() do - "documentation/**/*.md" + "documentation/**/*.{md,livemd,cheatmd}" |> Path.wildcard() |> Enum.map(fn path -> title = path |> Path.basename(".md") + |> Path.basename(".livemd") + |> Path.basename(".cheatmd") |> String.split(~r/[-_]/) - |> Enum.map(&String.capitalize/1) - |> Enum.join(" ") + |> Enum.map(&capitalize/1) |> case do "F A Q" -> "FAQ" @@ -88,19 +89,25 @@ defmodule AshPostgres.MixProject do end) end - defp groups_for_extras() do - "documentation/*" - |> Path.wildcard() - |> Enum.map(fn folder -> - name = - folder - |> Path.basename() - |> String.split(~r/[-_]/) - |> Enum.map(&String.capitalize/1) - |> Enum.join(" ") - - {name, folder |> Path.join("**") |> Path.wildcard()} + defp capitalize(string) do + string + |> String.split(" ") + |> Enum.map(fn string -> + [hd | tail] = String.graphemes(string) + String.capitalize(hd) <> Enum.join(tail) end) + |> Enum.join(" ") + end + + defp groups_for_extras() do + [ + Tutorials: [ + ~r'documentation/tutorials' + ], + "How To": ~r'documentation/how_to', + Topics: ~r'documentation/topics', + DSLs: ~r'documentation/dsls' + ] end defp docs do @@ -135,12 +142,24 @@ defmodule AshPostgres.MixProject do AshPostgres.Repo, AshPostgres.DataLayer ], + Utilities: [ + AshPostgres.ManualRelationship + ], Introspection: [ - AshPostgres.DataLayer.Info + AshPostgres.DataLayer.Info, + AshPostgres.CheckConstraint, + AshPostgres.CustomExtension, + AshPostgres.CustomIndex, + AshPostgres.Reference, + AshPostgres.Statement ], - "Postgres Expressions": [ - AshPostgres.Functions.Fragment, - AshPostgres.Functions.TrigramSimilarity + Types: [ + AshPostgres.Type, + AshPostgres.Tsquery, + AshPostgres.Tsvector + ], + Extensions: [ + AshPostgres.Extensions.Vector, ], "Custom Aggregates": [ AshPostgres.CustomAggregate @@ -149,7 +168,13 @@ defmodule AshPostgres.MixProject do AshPostgres.Migration, EctoMigrationDefault ], - Transformers: ~r/AshPostgres\.Transformers\..*/, + Expressions: [ + AshPostgres.Functions.Fragment, + AshPostgres.Functions.TrigramSimilarity, + AshPostgres.Functions.ILike, + AshPostgres.Functions.Like, + AshPostgres.Functions.VectorCosineDistance + ], Internals: ~r/.*/ ] ] @@ -197,8 +222,15 @@ defmodule AshPostgres.MixProject do sobelow: "sobelow --skip -i Config.Secrets --ignore-files lib/migration_generator/migration_generator.ex", credo: "credo --strict", - docs: ["docs", "ash.replace_doc_links"], + docs: [ + "spark.cheat_sheets", + "docs", + "ash.replace_doc_links", + "spark.cheat_sheets_in_search" + ], "spark.formatter": "spark.formatter --extensions AshPostgres.DataLayer", + "spark.cheat_sheets": "spark.cheat_sheets --extensions AshPostgres.DataLayer", + "spark.cheat_sheets_in_search": "spark.cheat_sheets_in_search --extensions AshPostgres.DataLayer", "test.generate_migrations": "ash_postgres.generate_migrations", "test.check_migrations": "ash_postgres.generate_migrations --check", "test.migrate_tenants": "ash_postgres.migrate --tenants", diff --git a/mix.lock b/mix.lock index 90c0fe6a..b91c40cc 100644 --- a/mix.lock +++ b/mix.lock @@ -7,14 +7,14 @@ "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, - "ex_doc": {:hex, :ex_doc, "0.28.3", "6eea2f69995f5fba94cd6dd398df369fe4e777a47cd887714a0976930615c9e6", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "05387a6a2655b5f9820f3f627450ed20b4325c25977b2ee69bed90af6688e718"}, + "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, @@ -23,12 +23,12 @@ "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, From 612c9ee7a94b39e30074be0b83428ac0ae52774a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Sep 2023 16:15:06 -0400 Subject: [PATCH 0035/1215] chore: map+join -> map_join --- mix.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 6d171cdb..a9da3b32 100644 --- a/mix.exs +++ b/mix.exs @@ -92,11 +92,10 @@ defmodule AshPostgres.MixProject do defp capitalize(string) do string |> String.split(" ") - |> Enum.map(fn string -> + |> Enum.map_join(" ", fn string -> [hd | tail] = String.graphemes(string) String.capitalize(hd) <> Enum.join(tail) end) - |> Enum.join(" ") end defp groups_for_extras() do From d2f8ba196036d80c0ec0a5bc0231d5e0a12b2462 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Sep 2023 16:18:46 -0400 Subject: [PATCH 0036/1215] chore: fix mix docs --- mix.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index a9da3b32..1593397a 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,7 @@ defmodule AshPostgres.MixProject do |> Path.basename(".livemd") |> Path.basename(".cheatmd") |> String.split(~r/[-_]/) - |> Enum.map(&capitalize/1) + |> Enum.map_join(" ", &capitalize/1) |> case do "F A Q" -> "FAQ" @@ -92,7 +92,7 @@ defmodule AshPostgres.MixProject do defp capitalize(string) do string |> String.split(" ") - |> Enum.map_join(" ", fn string -> + |> Enum.map(fn string -> [hd | tail] = String.graphemes(string) String.capitalize(hd) <> Enum.join(tail) end) From cca5f0244b36e1fb76d2b2561af5cb4db9f1a067 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Sep 2023 17:04:39 -0400 Subject: [PATCH 0037/1215] chore: fix type names I just broke --- lib/data_layer.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b99538bc..4fd22b30 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -311,7 +311,7 @@ defmodule AshPostgres.DataLayer do doc: "Skip generating unique indexes when generating migrations" ], unique_index_names: [ - type: {:or, [{:tuple, [{:list, :atom}, :string]}, {:tuple, [{:list, :atom}, :string, :string]}]}, + type: {:list, {:or, [{:tuple, [{:list, :atom}, :string]}, {:tuple, [{:list, :atom}, :string, :string]}]}}, default: [], doc: """ A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` @@ -332,7 +332,7 @@ defmodule AshPostgres.DataLayer do """ ], foreign_key_names: [ - type: {:or, [{:tuple, [:atom, :string]}, {:tuple, [:string, :string]}]}, + type: {:list, {:or, [{:tuple, [:atom, :string]}, {:tuple, [:string, :string]}]}}, default: [], doc: """ A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` From b00dcf20c94a9ba396e4d3d423f5c6bb6cb39193 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Sep 2023 17:16:47 -0400 Subject: [PATCH 0038/1215] chore: format & fix docs --- documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd | 4 ++-- lib/data_layer.ex | 5 ++++- mix.exs | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index 4cc4e571..cdde5edd 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -41,10 +41,10 @@ end | `base_filter_sql` | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | | `simple_join_first_aggregates` | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | | `skip_unique_indexes` | `list(atom) \| atom` | `false` | Skip generating unique indexes when generating migrations | -| `unique_index_names` | `{list(atom), String.t} \| {list(atom), String.t, String.t}` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | +| `unique_index_names` | `list({list(atom), String.t} \| {list(atom), String.t, String.t})` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | | `exclusion_constraint_names` | ``any`` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | | `identity_index_names` | ``any`` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | -| `foreign_key_names` | `{atom, String.t} \| {String.t, String.t}` | `[]` | A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` | +| `foreign_key_names` | `list({atom, String.t} \| {String.t, String.t})` | `[]` | A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` | | `migration_ignore_attributes` | `list(atom)` | `[]` | A list of attributes that will be ignored when generating migrations. | | `table` | `String.t` | | The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. | | `schema` | `String.t` | | The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. | diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 4fd22b30..0c50b5d2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -311,7 +311,10 @@ defmodule AshPostgres.DataLayer do doc: "Skip generating unique indexes when generating migrations" ], unique_index_names: [ - type: {:list, {:or, [{:tuple, [{:list, :atom}, :string]}, {:tuple, [{:list, :atom}, :string, :string]}]}}, + type: + {:list, + {:or, + [{:tuple, [{:list, :atom}, :string]}, {:tuple, [{:list, :atom}, :string, :string]}]}}, default: [], doc: """ A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` diff --git a/mix.exs b/mix.exs index 1593397a..12a4d99b 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do AshPostgres.Tsvector ], Extensions: [ - AshPostgres.Extensions.Vector, + AshPostgres.Extensions.Vector ], "Custom Aggregates": [ AshPostgres.CustomAggregate @@ -229,7 +229,8 @@ defmodule AshPostgres.MixProject do ], "spark.formatter": "spark.formatter --extensions AshPostgres.DataLayer", "spark.cheat_sheets": "spark.cheat_sheets --extensions AshPostgres.DataLayer", - "spark.cheat_sheets_in_search": "spark.cheat_sheets_in_search --extensions AshPostgres.DataLayer", + "spark.cheat_sheets_in_search": + "spark.cheat_sheets_in_search --extensions AshPostgres.DataLayer", "test.generate_migrations": "ash_postgres.generate_migrations", "test.check_migrations": "ash_postgres.generate_migrations --check", "test.migrate_tenants": "ash_postgres.migrate --tenants", From 99e80294993b4f603c09668080b9d6c654b17593 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Sep 2023 15:32:20 -0400 Subject: [PATCH 0039/1215] improvement: fix `upsert_fields` behavior for upserts --- config/config.exs | 2 -- lib/data_layer.ex | 21 +++++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/config/config.exs b/config/config.exs index 7e974dba..0b18acfa 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,7 +1,5 @@ import Config -config :ash, :use_all_identities_in_manage_relationship?, false - if Mix.env() == :dev do config :git_ops, mix_project: AshPostgres.MixProject, diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 0c50b5d2..775c0425 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1151,8 +1151,15 @@ defmodule AshPostgres.DataLayer do opts = if options[:upsert?] do + on_conflict = + if options[:upsert_set] do + [set: options[:upsert_set]] + else + {:replace, options[:upsert_fields] || []} + end + opts - |> Keyword.put(:on_conflict, {:replace, options[:upsert_fields] || []}) + |> Keyword.put(:on_conflict, on_conflict) |> Keyword.put( :conflict_target, conflict_target( @@ -1761,12 +1768,22 @@ defmodule AshPostgres.DataLayer do |> update_defaults() |> Keyword.merge(explicitly_changing_attributes) + on_conflict = + if changeset.context[:private][:upsert_fields] do + Keyword.take( + on_conflict, + changeset.context[:private][:upsert_fields] + ) + else + on_conflict + end + case bulk_create(resource, [changeset], %{ single?: true, upsert?: true, tenant: changeset.tenant, upsert_keys: keys, - upsert_fields: Keyword.keys(on_conflict), + upsert_set: on_conflict, return_records?: true }) do {:ok, [result]} -> From 0adec1d1634387367bb818e40711e34ceb8cb3e4 Mon Sep 17 00:00:00 2001 From: Alessio Montagnani Date: Mon, 25 Sep 2023 21:33:28 +0200 Subject: [PATCH 0040/1215] * improvement: in multitenant resources migration's generation, check if the relationship points at the primary key of the target then not adding the multitenancy attribute (#144 and #157) --- lib/migration_generator/operation.ex | 4 +- test/migration_generator_test.exs | 107 +++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 1c9a2b00..abd9acbf 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -93,7 +93,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = attribute }) do with_match = - if destination_attribute != reference_attribute do + if !reference.primary_key? && destination_attribute != reference_attribute do "with: [#{as_atom(source_attribute)}: :#{as_atom(destination_attribute)}], match: :full" end @@ -485,7 +485,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do end with_match = - if destination_attribute != reference_attribute do + if !reference.primary_key? && destination_attribute != reference_attribute do "with: [#{as_atom(source_attribute)}: :#{as_atom(destination_attribute)}], match: :full" end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index a76fe3ce..785cff05 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -60,6 +60,29 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + defmacrop defresource(mod, table, do: body) do + quote do + Code.compiler_options(ignore_module_conflict: true) + + defmodule unquote(mod) do + use Ash.Resource, data_layer: AshPostgres.DataLayer + + postgres do + table unquote(table) + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + unquote(body) + end + + Code.compiler_options(ignore_module_conflict: false) + end + end + describe "creating initial snapshots" do setup do on_exit(fn -> @@ -877,6 +900,90 @@ defmodule AshPostgres.MigrationGeneratorTest do assert after_drop =~ ~S[references(:posts] end + + test "references with added only when needed on multitenant resources" do + defresource Org, "orgs" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + end + + defresource User, "users" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:secondary_id, :uuid) + attribute(:name, :string) + attribute(:org_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + end + end + + defresource UserThing1, "user_things1" do + attributes do + attribute(:id, :string, primary_key?: true, allow_nil?: false) + attribute(:name, :string) + attribute(:org_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + belongs_to(:user, User, destination_attribute: :secondary_id) + end + end + + defresource UserThing2, "user_things2" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + belongs_to(:user, User) + end + end + + defapi([Org, User, UserThing1, UserThing2]) + + AshPostgres.MigrationGenerator.generate(Api, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ + ~S[references(:users, column: :secondary_id, with: [org_id: :org_id\], match: :full, name: "user_things1_user_id_fkey", type: :uuid, prefix: "public")] + + assert File.read!(file) =~ + ~S[references(:users, column: :id, name: "user_things2_user_id_fkey", type: :uuid, prefix: "public")] + end end describe "check constraints" do From 793b6a97687242227563d5f532b1e9b9524488d9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 26 Sep 2023 11:45:07 -0400 Subject: [PATCH 0041/1215] chore: release version v1.3.52 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c5faf63..a6ea77c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.52](https://github.com/ash-project/ash_postgres/compare/v1.3.51...v1.3.52) (2023-09-26) + + + + +### Bug Fixes: + +* use `:wrap_list` type instead of custom validaitons (#167) + +### Improvements: + +* fix `upsert_fields` behavior for upserts + +* support data_layer_context option on transactions + ## [v1.3.51](https://github.com/ash-project/ash_postgres/compare/v1.3.50...v1.3.51) (2023-09-20) diff --git a/mix.exs b/mix.exs index 12a4d99b..1bcf32ae 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.51" + @version "1.3.52" def project do [ From 472b17ce64f774834d7d9028880f3653d0f1a05b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 26 Sep 2023 23:17:47 -0400 Subject: [PATCH 0042/1215] docs: update spark for docs improvements --- .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 1073 +++++++++++++++-- mix.exs | 9 +- mix.lock | 8 +- 3 files changed, 1012 insertions(+), 78 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index cdde5edd..d4217b88 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -1,3 +1,6 @@ + # DSL: AshPostgres.DataLayer A postgres data layer that leverages Ecto's postgres capabilities. @@ -32,23 +35,330 @@ end ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `repo`* | `atom` | | The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more | -| `migrate?` | `boolean` | `true` | Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` | -| `migration_types` | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | -| `migration_defaults` | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | -| `base_filter_sql` | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | -| `simple_join_first_aggregates` | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | -| `skip_unique_indexes` | `list(atom) \| atom` | `false` | Skip generating unique indexes when generating migrations | -| `unique_index_names` | `list({list(atom), String.t} \| {list(atom), String.t, String.t})` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | -| `exclusion_constraint_names` | ``any`` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | -| `identity_index_names` | ``any`` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | -| `foreign_key_names` | `list({atom, String.t} \| {String.t, String.t})` | `[]` | A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` | -| `migration_ignore_attributes` | `list(atom)` | `[]` | A list of attributes that will be ignored when generating migrations. | -| `table` | `String.t` | | The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. | -| `schema` | `String.t` | | The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. | -| `polymorphic?` | `boolean` | `false` | Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. | + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + repo + + + * + + + atom + + + + The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more +
+ + + migrate? + + + + + boolean + + true + + Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` +
+ + + migration_types + + + + + Keyword.t + + [] + + A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. +
+ + + migration_defaults + + + + + Keyword.t + + [] + + A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. + +
+ + + base_filter_sql + + + + + String.t + + + + A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter +
+ + + simple_join_first_aggregates + + + + + list(atom) + + [] + + A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. + +
+ + + skip_unique_indexes + + + + + list(atom) | atom + + false + + Skip generating unique indexes when generating migrations +
+ + + unique_index_names + + + + + list({list(atom), String.t} | {list(atom), String.t, String.t}) + + [] + + A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` + +
+ + + exclusion_constraint_names + + + + + `any` + + [] + + A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` + +
+ + + identity_index_names + + + + + `any` + + [] + + A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. + +
+ + + foreign_key_names + + + + + list({atom, String.t} | {String.t, String.t}) + + [] + + A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` + +
+ + + migration_ignore_attributes + + + + + list(atom) + + [] + + A list of attributes that will be ignored when generating migrations. + +
+ + + table + + + + + String.t + + + + The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. + +
+ + + schema + + + + + String.t + + + + The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. + +
+ + + polymorphic? + + + + + boolean + + false + + Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. + +
## postgres.custom_indexes @@ -91,21 +401,201 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" -### Arguments -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `fields` | `list(atom \| String.t) \| atom \| String.t` | | The fields to include in the index. | + ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `name` | `String.t` | | the name of the index. Defaults to "#{table}_#{column}_index". | -| `unique` | `boolean` | `false` | indicates whether the index should be unique. | -| `concurrently` | `boolean` | `false` | indicates whether the index should be created/dropped concurrently. | -| `using` | `String.t` | | configures the index type. | -| `prefix` | `String.t` | | specify an optional prefix for the index. | -| `where` | `String.t` | | specify conditions for a partial index. | -| `message` | `String.t` | | A custom message to use for unique indexes that have been violated | -| `include` | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + fields + + + + + list(atom | String.t) | atom | String.t + + + + The fields to include in the index. +
+ + + name + + + + + String.t + + + + the name of the index. Defaults to "#{table}_#{column}_index". +
+ + + unique + + + + + boolean + + false + + indicates whether the index should be unique. +
+ + + concurrently + + + + + boolean + + false + + indicates whether the index should be created/dropped concurrently. +
+ + + using + + + + + String.t + + + + configures the index type. +
+ + + prefix + + + + + String.t + + + + specify an optional prefix for the index. +
+ + + where + + + + + String.t + + + + specify conditions for a partial index. +
+ + + message + + + + + String.t + + + + A custom message to use for unique indexes that have been violated +
+ + + include + + + + + list(String.t) + + + + specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. +
@@ -171,16 +661,107 @@ end -### Arguments -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `name`* | `atom` | | The name of the statement, must be unique within the resource | + ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `up`* | `String.t` | | How to create the structure of the statement | -| `down`* | `String.t` | | How to tear down the structure of the statement | -| `code?` | `boolean` | `false` | By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + name + + + * + + + atom + + + + The name of the statement, must be unique within the resource + +
+ + + up + + + * + + + String.t + + + + How to create the structure of the statement + +
+ + + down + + + * + + + String.t + + + + How to tear down the structure of the statement +
+ + + code? + + + + + boolean + + false + + By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations + +
@@ -211,11 +792,81 @@ end ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `template`* | `list(String.t \| atom) \| String.t \| atom` | | A template that will cause the resource to create/manage the specified schema. | -| `create?` | `boolean` | `true` | Whether or not to automatically create a tenant when a record is created | -| `update?` | `boolean` | `true` | Whether or not to automatically update the tenant name if the record is udpated | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + template + + + * + + + list(String.t | atom) | String.t | atom + + + + A template that will cause the resource to create/manage the specified schema. + +
+ + + create? + + + + + boolean + + true + + Whether or not to automatically create a tenant when a record is created +
+ + + update? + + + + + boolean + + true + + Whether or not to automatically update the tenant name if the record is udpated +
@@ -243,11 +894,79 @@ end ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `polymorphic_on_delete` | `:delete \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | -| `polymorphic_on_update` | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | -| `polymorphic_name` | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + polymorphic_on_delete + + + + + :delete | :nilify | :nothing | :restrict + + + + For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. +
+ + + polymorphic_on_update + + + + + :update | :nilify | :nothing | :restrict + + + + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. +
+ + + polymorphic_name + + + + + :update | :nilify | :nothing | :restrict + + + + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. +
@@ -275,18 +994,145 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post -### Arguments -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `relationship`* | `atom` | | The relationship to be configured | + ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `ignore?` | `boolean` | | If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way | -| `on_delete` | `:delete \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | -| `on_update` | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | -| `deferrable` | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | -| `name` | `String.t` | | The name of the foreign key to generate in the database. Defaults to __fkey | + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + relationship + + + * + + + atom + + + + The relationship to be configured +
+ + + ignore? + + + + + boolean + + + + If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way +
+ + + on_delete + + + + + :delete | :nilify | :nothing | :restrict + + + + What should happen to records of this resource when the referenced record of the *destination* resource is deleted. + +
+ + + on_update + + + + + :update | :nilify | :nothing | :restrict + + + + What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. + +
+ + + deferrable + + + + + false | true | :initially + + false + + Wether or not the constraint is deferrable. This only affects the migration generator. + +
+ + + name + + + + + String.t + + + + The name of the foreign key to generate in the database. Defaults to __fkey + + + + +
@@ -343,16 +1189,103 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: -### Arguments -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `attribute`* | ``any`` | | The attribute or list of attributes to which an error will be added if the check constraint fails | -| `name`* | `String.t` | | The name of the constraint | + ### Options -| Name | Type | Default | Docs | -| --- | --- | --- | --- | -| `message` | `String.t` | | The message to be added if the check constraint fails | -| `check` | `String.t` | | The contents of the check. If this is set, the migration generator will include it when generating migrations | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDocs
+ + + attribute + + + * + + + `any` + + + + The attribute or list of attributes to which an error will be added if the check constraint fails +
+ + + name + + + * + + + String.t + + + + The name of the constraint +
+ + + message + + + + + String.t + + + + The message to be added if the check constraint fails +
+ + + check + + + + + String.t + + + + The contents of the check. If this is set, the migration generator will include it when generating migrations +
diff --git a/mix.exs b/mix.exs index 1bcf32ae..01476305 100644 --- a/mix.exs +++ b/mix.exs @@ -36,8 +36,8 @@ defmodule AshPostgres.MixProject do docs: docs(), aliases: aliases(), package: package(), - source_url: "https://github.com/ash-project/ash_postgres", - homepage_url: "https://github.com/ash-project/ash_postgres", + source_url: "https://github.com/ash-project/ash_postgres/", + homepage_url: "https://ash-hq.org/", consolidate_protocols: Mix.env() != :test ] end @@ -186,9 +186,10 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, + {:spark, "~> 1.1"}, {:ash, ash_version("~> 2.14 and >= 2.14.18")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, - {:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false}, + {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false}, @@ -224,7 +225,7 @@ defmodule AshPostgres.MixProject do docs: [ "spark.cheat_sheets", "docs", - "ash.replace_doc_links", + "spark.replace_doc_links", "spark.cheat_sheets_in_search" ], "spark.formatter": "spark.formatter --extensions AshPostgres.DataLayer", diff --git a/mix.lock b/mix.lock index b91c40cc..109d5fdb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.18", "ac2fd2f274f4989d3c71de3df9a603941bc47ac6c8d27006df78f78844114969", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ec44ad258eb71a2dd5210f67bd882698ea112f6dad79505b156594be06e320e5"}, + "ash": {:hex, :ash, "2.14.21", "8ae4205bbc531098b417fe482b50afffdce102e46df3bc00a60f0dfb3afb1aab", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7434b4076c1e8c896989ba6d4107d71d969b88ce6417fe659a15b56ed01df5e"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -7,14 +7,14 @@ "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, - "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, + "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "16a8f536d1a0868293a30d63bcff6510bf023de3", []}, "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, @@ -34,7 +34,7 @@ "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.39", "f143b84a5b796bf2d83ec8fb4793ee9e66e67510c40d785f9a67050bb88e7677", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "d71bc26014c7e7abcdcf553f4cf7c5a5ff96f8365b1e20be3768ce503aafb203"}, + "spark": {:hex, :spark, "1.1.40", "b61438fece40eb0ffed7c4c9e5f1c2c817209902ed853b0008b7681b1994c29c", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "40d0f803f1090249ef6a76cb2bf40466c57f4995326dc97996e0b8b4f365ad17"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, From d0e901b7596446e16d94211bd678bbc544288240 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Oct 2023 08:34:45 -0400 Subject: [PATCH 0043/1215] chore: analytics in hexdocs --- mix.exs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mix.exs b/mix.exs index 01476305..b3f0c220 100644 --- a/mix.exs +++ b/mix.exs @@ -115,6 +115,13 @@ defmodule AshPostgres.MixProject do source_ref: "v#{@version}", logo: "logos/small-logo.png", extras: extras(), + before_closing_head_tag: fn type -> + if type == :html do + """ + + """ + end + end, spark: [ mix_tasks: [ Postgres: [ From 07fd9920ab274c2c8b24aaec4a15d1bca070db55 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Oct 2023 10:56:05 -0400 Subject: [PATCH 0044/1215] chore: only gather analytics on hexdocs --- mix.exs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index b3f0c220..20cde184 100644 --- a/mix.exs +++ b/mix.exs @@ -118,7 +118,15 @@ defmodule AshPostgres.MixProject do before_closing_head_tag: fn type -> if type == :html do """ - + """ end end, From 5fdc81bf06db7c1446c6db9ed364362d8e274901 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Oct 2023 12:56:44 -0400 Subject: [PATCH 0045/1215] fix: subquery aggregate if limit is applied --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 775c0425..1f3a6ffb 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -701,7 +701,7 @@ defmodule AshPostgres.DataLayer do query = default_bindings(query, resource) query = - if query.distinct do + if query.distinct || query.limit do query = query |> Ecto.Query.exclude(:select) From 270d8685e7031d261e43703b8ace4621ef98dba6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 9 Oct 2023 18:57:06 -0400 Subject: [PATCH 0046/1215] improvement: support to-one references in calculations --- lib/calculation.ex | 10 ++++++++ lib/expr.ex | 8 +++++++ lib/join.ex | 1 - lib/sort.ex | 19 +++++++++++++++ mix.exs | 2 +- mix.lock | 3 ++- test/calculation_test.exs | 42 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 2 ++ 8 files changed, 84 insertions(+), 3 deletions(-) diff --git a/lib/calculation.ex b/lib/calculation.ex index de34819a..81c68aaf 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -8,6 +8,16 @@ defmodule AshPostgres.Calculation do def add_calculations(query, calculations, resource, source_binding) do query = AshPostgres.DataLayer.default_bindings(query, resource) + {:ok, query} = + AshPostgres.Join.join_all_relationships( + query, + %Ash.Filter{ + resource: resource, + expression: Enum.map(calculations, &elem(&1, 1)) + }, + left_only?: true + ) + aggregates = calculations |> Enum.flat_map(fn {_calculation, expression} -> diff --git a/lib/expr.ex b/lib/expr.ex index 7a0eb8c9..cb988819 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -944,6 +944,14 @@ defmodule AshPostgres.Expr do end end) + if is_nil(binding_to_replace) do + raise """ + Error building calculation reference: #{inspect(relationship_path)} is not available in bindings. + + In reference: #{ref} + """ + end + temp_bindings = bindings.bindings |> Map.delete(0) diff --git a/lib/join.ex b/lib/join.ex index 83c5f4cb..525915ef 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -40,7 +40,6 @@ defmodule AshPostgres.Join do filter |> Ash.Filter.map(fn %Ash.Query.Parent{} -> - # Removing any `This` from the filter nil other -> diff --git a/lib/sort.ex b/lib/sort.ex index a67a1d56..08ed7a78 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -44,6 +44,25 @@ defmodule AshPostgres.Sort do [] end) + calcs = + Enum.flat_map(sort, fn + {%Ash.Query.Calculation{} = calculation, _} -> + [calculation] + + _ -> + [] + end) + + {:ok, query} = + AshPostgres.Join.join_all_relationships( + query, + %Ash.Filter{ + resource: resource, + expression: calcs + }, + left_only?: true + ) + case AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do {:error, error} -> {:error, error} diff --git a/mix.exs b/mix.exs index 20cde184..d24257b4 100644 --- a/mix.exs +++ b/mix.exs @@ -202,7 +202,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:spark, "~> 1.1"}, - {:ash, ash_version("~> 2.14 and >= 2.14.18")}, + {:ash, ash_version("~> 2.15 and >= 2.15.10")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 109d5fdb..7a235c0e 100644 --- a/mix.lock +++ b/mix.lock @@ -7,6 +7,7 @@ "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, @@ -34,7 +35,7 @@ "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.40", "b61438fece40eb0ffed7c4c9e5f1c2c817209902ed853b0008b7681b1994c29c", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "40d0f803f1090249ef6a76cb2bf40466c57f4995326dc97996e0b8b4f365ad17"}, + "spark": {:hex, :spark, "1.1.43", "5817cefa41c6f7105989fa40c044c05bf2cab7b81c8ecbd963bdbdf6eeabc85a", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "29e42b900f3a7666e67fba270ff10d7b9fc693c8c2179b6bd65aa6b8426d30ca"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 2e6e81a0..9cfe8b61 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -60,6 +60,48 @@ defmodule AshPostgres.CalculationTest do |> Api.read!() end + test "calculations can refer to to_one path attributes in filters" do + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Foo", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Api.create!() + + Post + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + assert [%{author_first_name_calc: "Foo"}] = + Post + |> Ash.Query.filter(author_first_name_calc == "Foo") + |> Ash.Query.load(:author_first_name_calc) + |> Api.read!() + end + + test "calculations can refer to to_one path attributes in sorts" do + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Foo", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Api.create!() + + Post + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + assert [%{author_first_name_calc: "Foo"}] = + Post + |> Ash.Query.sort(:author_first_name_calc) + |> Ash.Query.load(:author_first_name_calc) + |> Api.read!() + end + test "calculations can refer to embedded attributes" do author = Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e3074e5a..6be836d3 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -238,6 +238,8 @@ defmodule AshPostgres.Test.Post do :string, CalculatePostPriceStringWithSymbol ) + + calculate(:author_first_name_calc, :string, expr(author.first_name)) end aggregates do From 772c12b5a32dc03db50e1a385ce21cf0f28ee2bb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 09:43:09 -0400 Subject: [PATCH 0047/1215] fix: don't run main query if only `exists` aggs are specified fixes: #170 --- lib/data_layer.ex | 32 +++++++++++++++++++++++--------- mix.lock | 2 +- test/aggregate_test.exs | 10 ++++++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1f3a6ffb..776ce90f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -734,9 +734,16 @@ defmodule AshPostgres.DataLayer do end ) - {:ok, - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) - |> add_exists_aggs(resource, query_before_select, exists)} + result = + case aggregates do + [] -> + %{} + + _ -> + dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + end + + {:ok, add_exists_aggs(result, resource, query_before_select, exists)} end defp add_exists_aggs(result, resource, query, exists) do @@ -812,12 +819,19 @@ defmodule AshPostgres.DataLayer do end ) - {:ok, - dynamic_repo(source_resource, query).one( - query, - repo_opts(nil, nil, source_resource) - ) - |> add_exists_aggs(source_resource, subquery, exists)} + result = + case aggregates do + [] -> + %{} + + _ -> + dynamic_repo(source_resource, query).one( + query, + repo_opts(nil, nil, source_resource) + ) + end + + {:ok, add_exists_aggs(result, source_resource, subquery, exists)} {:error, error} -> {:error, error} diff --git a/mix.lock b/mix.lock index 7a235c0e..c4e44909 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.14.21", "8ae4205bbc531098b417fe482b50afffdce102e46df3bc00a60f0dfb3afb1aab", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7434b4076c1e8c896989ba6d4107d71d969b88ce6417fe659a15b56ed01df5e"}, + "ash": {:hex, :ash, "2.15.10", "e5311e05f1ac0ad0f1c4a0a032d6fd671803d8b3c24cc251e139aa2c0593d26b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2f8e175be13eee0fdd06b88117d223cecd13994984c9131e0975d23f032ae6a1"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 78e420f2..4356e686 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -261,6 +261,11 @@ defmodule AshPostgres.AggregateTest do |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() + post2 = + Post + |> Ash.Changeset.new(%{title: "title2"}) + |> Api.create!() + refute Post |> Ash.Query.filter(has_comment_called_match) |> Api.exists?() @@ -270,6 +275,11 @@ defmodule AshPostgres.AggregateTest do |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Api.create!() + Comment + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + assert Post |> Api.exists?() refute Post |> Api.exists?(query: [filter: [title: "non-match"]]) From 9f62d65215a6a22f657600baa23ae979ea5d61cf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 14:12:25 -0400 Subject: [PATCH 0048/1215] improvement: support `:ci_string` as a storage_type --- lib/migration_generator/migration_generator.ex | 1 + lib/types/ci_string_wrapper copy.ex | 14 -------------- lib/types/ci_string_wrapper.ex | 10 +++++----- lib/types/string_wrapper.ex | 14 ++++++++++++++ 4 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 lib/types/ci_string_wrapper copy.ex create mode 100644 lib/types/string_wrapper.ex diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2fc59c63..a15a058f 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2743,6 +2743,7 @@ defmodule AshPostgres.MigrationGenerator do end defp migration_type_from_storage_type(:string), do: :text + defp migration_type_from_storage_type(:ci_string), do: :citext defp migration_type_from_storage_type(storage_type), do: storage_type defp foreign_key?(relationship) do diff --git a/lib/types/ci_string_wrapper copy.ex b/lib/types/ci_string_wrapper copy.ex deleted file mode 100644 index 72f850d4..00000000 --- a/lib/types/ci_string_wrapper copy.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Ash.Type.CiStringWrapper do - @moduledoc false - use Ash.Type - - @impl true - def storage_type(_), do: :citext - - @impl true - defdelegate cast_input(value, constraints), to: Ash.Type.CiString - @impl true - defdelegate cast_stored(value, constraints), to: Ash.Type.CiString - @impl true - defdelegate dump_to_native(value, constraints), to: Ash.Type.CiString -end diff --git a/lib/types/ci_string_wrapper.ex b/lib/types/ci_string_wrapper.ex index 82d067da..72f850d4 100644 --- a/lib/types/ci_string_wrapper.ex +++ b/lib/types/ci_string_wrapper.ex @@ -1,14 +1,14 @@ -defmodule Ash.Type.StringWrapper do +defmodule Ash.Type.CiStringWrapper do @moduledoc false use Ash.Type @impl true - def storage_type(_), do: :text + def storage_type(_), do: :citext @impl true - defdelegate cast_input(value, constraints), to: Ash.Type.String + defdelegate cast_input(value, constraints), to: Ash.Type.CiString @impl true - defdelegate cast_stored(value, constraints), to: Ash.Type.String + defdelegate cast_stored(value, constraints), to: Ash.Type.CiString @impl true - defdelegate dump_to_native(value, constraints), to: Ash.Type.String + defdelegate dump_to_native(value, constraints), to: Ash.Type.CiString end diff --git a/lib/types/string_wrapper.ex b/lib/types/string_wrapper.ex new file mode 100644 index 00000000..82d067da --- /dev/null +++ b/lib/types/string_wrapper.ex @@ -0,0 +1,14 @@ +defmodule Ash.Type.StringWrapper do + @moduledoc false + use Ash.Type + + @impl true + def storage_type(_), do: :text + + @impl true + defdelegate cast_input(value, constraints), to: Ash.Type.String + @impl true + defdelegate cast_stored(value, constraints), to: Ash.Type.String + @impl true + defdelegate dump_to_native(value, constraints), to: Ash.Type.String +end From f67db5ee6d767a228aefcde395f1d1d823f0e2a5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 14:16:25 -0400 Subject: [PATCH 0049/1215] chore: handle ci_string type as storage type --- lib/types/types.ex | 18 +++++++++++++++++- test/aggregate_test.exs | 7 +++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/types/types.ex b/lib/types/types.ex index 220ae1e1..04e4306c 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -46,12 +46,28 @@ defmodule AshPostgres.Types do end if cast_in_query? do - parameterized_type(Ash.Type.ecto_type(type), constraints) + type = Ash.Type.ecto_type(type) + + type = + if type.type(constraints) == :ci_string do + Ash.Type.CiStringWrapper + else + type + end + + parameterized_type(type, constraints) else nil end else if is_atom(type) && :erlang.function_exported(type, :type, 1) do + type = + if type == :ci_string do + :citext + else + type + end + {:parameterized, type, constraints || []} else type diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 4356e686..6089c074 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -261,10 +261,9 @@ defmodule AshPostgres.AggregateTest do |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() - post2 = - Post - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + Post + |> Ash.Changeset.new(%{title: "title2"}) + |> Api.create!() refute Post |> Ash.Query.filter(has_comment_called_match) From c58ff4e514b23b99c7a6cb8aef5531e11586a340 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 15:32:29 -0400 Subject: [PATCH 0050/1215] improvement: update ash dependency --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index d24257b4..89eff62a 100644 --- a/mix.exs +++ b/mix.exs @@ -202,7 +202,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:spark, "~> 1.1"}, - {:ash, ash_version("~> 2.15 and >= 2.15.10")}, + {:ash, ash_version("~> 2.15 and >= 2.15.12")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, From 5805f07d69427460d4cdb0594f9c5f9727fa2d87 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 15:32:39 -0400 Subject: [PATCH 0051/1215] chore: release version v1.3.53 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6ea77c5..51b8dc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.53](https://github.com/ash-project/ash_postgres/compare/v1.3.52...v1.3.53) (2023-10-10) + + + + +### Bug Fixes: + +* don't run main query if only `exists` aggs are specified + +* subquery aggregate if limit is applied + +### Improvements: + +* update ash dependency + +* support `:ci_string` as a storage_type + +* support to-one references in calculations + ## [v1.3.52](https://github.com/ash-project/ash_postgres/compare/v1.3.51...v1.3.52) (2023-09-26) diff --git a/mix.exs b/mix.exs index 89eff62a..5b0b971d 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.52" + @version "1.3.53" def project do [ From c6d71d74bf066fd2ecf276e38ecbe52274a86abb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 15:47:53 -0400 Subject: [PATCH 0052/1215] fix: fix type specification for foreign_key_names --- .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 132 +++++++++--------- lib/data_layer.ex | 8 +- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index d4217b88..cf0ac540 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -60,7 +60,7 @@ end atom
- + The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more @@ -74,7 +74,7 @@ end migrate? - + boolean @@ -94,7 +94,7 @@ end migration_types - + Keyword.t @@ -114,7 +114,7 @@ end migration_defaults - + Keyword.t @@ -135,13 +135,13 @@ end base_filter_sql - + String.t - + A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter @@ -155,7 +155,7 @@ end simple_join_first_aggregates - + list(atom) @@ -176,7 +176,7 @@ end skip_unique_indexes - + list(atom) | atom @@ -196,7 +196,7 @@ end unique_index_names - + list({list(atom), String.t} | {list(atom), String.t, String.t}) @@ -217,7 +217,7 @@ end exclusion_constraint_names - + `any` @@ -238,7 +238,7 @@ end identity_index_names - + `any` @@ -259,7 +259,7 @@ end foreign_key_names - + list({atom, String.t} | {String.t, String.t}) @@ -280,7 +280,7 @@ end migration_ignore_attributes - + list(atom) @@ -301,13 +301,13 @@ end table - + String.t - + The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. @@ -322,13 +322,13 @@ end schema - + String.t - + The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. @@ -343,7 +343,7 @@ end polymorphic? - + boolean @@ -421,13 +421,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" fields - + list(atom | String.t) | atom | String.t - + The fields to include in the index. @@ -441,13 +441,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" name - + String.t - + the name of the index. Defaults to "#{table}_#{column}_index". @@ -461,7 +461,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" unique - + boolean @@ -481,7 +481,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" concurrently - + boolean @@ -501,13 +501,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" using - + String.t - + configures the index type. @@ -521,13 +521,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" prefix - + String.t - + specify an optional prefix for the index. @@ -541,13 +541,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" where - + String.t - + specify conditions for a partial index. @@ -561,13 +561,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" message - + String.t - + A custom message to use for unique indexes that have been violated @@ -581,13 +581,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" include - + list(String.t) - + specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. @@ -688,7 +688,7 @@ end atom - + The name of the statement, must be unique within the resource @@ -710,7 +710,7 @@ end String.t - + How to create the structure of the statement @@ -732,7 +732,7 @@ end String.t - + How to tear down the structure of the statement @@ -746,7 +746,7 @@ end code? - + boolean @@ -817,7 +817,7 @@ end list(String.t | atom) | String.t | atom - + A template that will cause the resource to create/manage the specified schema. @@ -832,7 +832,7 @@ end create? - + boolean @@ -852,7 +852,7 @@ end update? - + boolean @@ -912,13 +912,13 @@ end polymorphic_on_delete - + :delete | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. @@ -932,13 +932,13 @@ end polymorphic_on_update - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -952,13 +952,13 @@ end polymorphic_name - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -1021,7 +1021,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post atom - + The relationship to be configured @@ -1035,13 +1035,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post ignore? - + boolean - + If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way @@ -1055,13 +1055,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_delete - + :delete | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced record of the *destination* resource is deleted. @@ -1076,13 +1076,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_update - + :update | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. @@ -1097,7 +1097,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post deferrable - + false | true | :initially @@ -1118,13 +1118,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post name - + String.t - + The name of the foreign key to generate in the database. Defaults to __fkey @@ -1216,7 +1216,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: `any`
- + The attribute or list of attributes to which an error will be added if the check constraint fails @@ -1237,7 +1237,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: String.t - + The name of the constraint @@ -1251,13 +1251,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: message - + String.t - + The message to be added if the check constraint fails @@ -1271,13 +1271,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: check - + String.t - + The contents of the check. If this is set, the migration generator will include it when generating migrations @@ -1294,9 +1294,3 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: ### Introspection Target: `AshPostgres.CheckConstraint` - - - - - - diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 776ce90f..1574ca47 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -335,7 +335,13 @@ defmodule AshPostgres.DataLayer do """ ], foreign_key_names: [ - type: {:list, {:or, [{:tuple, [:atom, :string]}, {:tuple, [:string, :string]}]}}, + type: + {:list, + {:or, + [ + {:tuple, [{:or, [:atom, :string]}, :string]}, + {:tuple, [{:or, [:atom, :string]}, :string, :string]} + ]}}, default: [], doc: """ A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` From 8c876f20b015f49bd9ef86798f1f8b526fa24e24 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 15:48:06 -0400 Subject: [PATCH 0053/1215] chore: release version v1.3.54 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b8dc86..b62f7881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.54](https://github.com/ash-project/ash_postgres/compare/v1.3.53...v1.3.54) (2023-10-10) + + + + +### Bug Fixes: + +* fix type specification for foreign_key_names + ## [v1.3.53](https://github.com/ash-project/ash_postgres/compare/v1.3.52...v1.3.53) (2023-10-10) diff --git a/mix.exs b/mix.exs index 5b0b971d..c7ef419f 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.53" + @version "1.3.54" def project do [ From a712cb38c1a8e1519725dec5cd3a6811fb7bc295 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 16:04:05 -0400 Subject: [PATCH 0054/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c4e44909..1b1b20fa 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.15.10", "e5311e05f1ac0ad0f1c4a0a032d6fd671803d8b3c24cc251e139aa2c0593d26b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2f8e175be13eee0fdd06b88117d223cecd13994984c9131e0975d23f032ae6a1"}, + "ash": {:hex, :ash, "2.15.12", "03da15f8c597dea4cce550e580feb19f3eb8a93ebf44d6b3b2c797d288d4a593", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9c240f7f80b508cd5d14e7e9d6000fa01800bc397247e3467dde8fc6cae0ac23"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 3e2826db04ad537137f99a69bcf275bc16f75b18 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Oct 2023 16:04:42 -0400 Subject: [PATCH 0055/1215] chore: update cheatsheets --- .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 134 +++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index cf0ac540..4652e444 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -60,7 +60,7 @@ end atom - + The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more @@ -74,7 +74,7 @@ end migrate? - + boolean @@ -94,7 +94,7 @@ end migration_types - + Keyword.t @@ -114,7 +114,7 @@ end migration_defaults - + Keyword.t @@ -135,13 +135,13 @@ end base_filter_sql - + String.t - + A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter @@ -155,7 +155,7 @@ end simple_join_first_aggregates - + list(atom) @@ -176,7 +176,7 @@ end skip_unique_indexes - + list(atom) | atom @@ -196,7 +196,7 @@ end unique_index_names - + list({list(atom), String.t} | {list(atom), String.t, String.t}) @@ -217,7 +217,7 @@ end exclusion_constraint_names - + `any` @@ -238,7 +238,7 @@ end identity_index_names - + `any` @@ -259,10 +259,10 @@ end foreign_key_names - + - list({atom, String.t} | {String.t, String.t}) + list({atom | String.t, String.t} | {atom | String.t, String.t, String.t}) [] @@ -280,7 +280,7 @@ end migration_ignore_attributes - + list(atom) @@ -301,13 +301,13 @@ end table - + String.t - + The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. @@ -322,13 +322,13 @@ end schema - + String.t - + The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. @@ -343,7 +343,7 @@ end polymorphic? - + boolean @@ -421,13 +421,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" fields - + list(atom | String.t) | atom | String.t - + The fields to include in the index. @@ -441,13 +441,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" name - + String.t - + the name of the index. Defaults to "#{table}_#{column}_index". @@ -461,7 +461,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" unique - + boolean @@ -481,7 +481,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" concurrently - + boolean @@ -501,13 +501,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" using - + String.t - + configures the index type. @@ -521,13 +521,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" prefix - + String.t - + specify an optional prefix for the index. @@ -541,13 +541,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" where - + String.t - + specify conditions for a partial index. @@ -561,13 +561,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" message - + String.t - + A custom message to use for unique indexes that have been violated @@ -581,13 +581,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" include - + list(String.t) - + specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. @@ -688,7 +688,7 @@ end atom - + The name of the statement, must be unique within the resource @@ -710,7 +710,7 @@ end String.t - + How to create the structure of the statement @@ -732,7 +732,7 @@ end String.t - + How to tear down the structure of the statement @@ -746,7 +746,7 @@ end code? - + boolean @@ -817,7 +817,7 @@ end list(String.t | atom) | String.t | atom - + A template that will cause the resource to create/manage the specified schema. @@ -832,7 +832,7 @@ end create? - + boolean @@ -852,7 +852,7 @@ end update? - + boolean @@ -912,13 +912,13 @@ end polymorphic_on_delete - + :delete | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. @@ -932,13 +932,13 @@ end polymorphic_on_update - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -952,13 +952,13 @@ end polymorphic_name - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -1021,7 +1021,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post atom - + The relationship to be configured @@ -1035,13 +1035,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post ignore? - + boolean - + If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way @@ -1055,13 +1055,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_delete - + :delete | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced record of the *destination* resource is deleted. @@ -1076,13 +1076,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_update - + :update | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. @@ -1097,7 +1097,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post deferrable - + false | true | :initially @@ -1118,13 +1118,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post name - + String.t - + The name of the foreign key to generate in the database. Defaults to __fkey @@ -1216,7 +1216,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: `any`
- + The attribute or list of attributes to which an error will be added if the check constraint fails @@ -1237,7 +1237,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: String.t - + The name of the constraint @@ -1251,13 +1251,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: message - + String.t - + The message to be added if the check constraint fails @@ -1271,13 +1271,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: check - + String.t - + The contents of the check. If this is set, the migration generator will include it when generating migrations @@ -1294,3 +1294,9 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: ### Introspection Target: `AshPostgres.CheckConstraint` + + + + + + From 1a6e469e5738e2636d0b47b9671e3a4031a541d9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 11 Oct 2023 15:17:50 -0400 Subject: [PATCH 0056/1215] improvement: support atomics on upserts --- lib/data_layer.ex | 360 +++++++++++++++++++++++--------------- mix.exs | 2 +- mix.lock | 6 +- test/atomics_test.exs | 31 ++++ test/bulk_create_test.exs | 1 + 5 files changed, 259 insertions(+), 141 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1574ca47..7e515784 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -439,6 +439,7 @@ defmodule AshPostgres.DataLayer do def can?(_, :transact), do: true def can?(_, :composite_primary_key), do: true def can?(_, {:atomic, :update}), do: true + def can?(_, {:atomic, :upsert}), do: true def can?(_, :upsert), do: true def can?(_, :changeset_filter), do: true @@ -1169,13 +1170,40 @@ defmodule AshPostgres.DataLayer do opts end + changesets = Enum.to_list(stream) + opts = if options[:upsert?] do + # Ash groups changesets by atomics before dispatching them to the data layer + # this means that all changesets have the same atomics + %{atomics: atomics, filters: filters} = Enum.at(changesets, 0) + + query = from(row in resource, as: ^0) + + query = + query + |> default_bindings(resource) + + upsert_set = + upsert_set(resource, changesets, options) + on_conflict = - if options[:upsert_set] do - [set: options[:upsert_set]] - else - {:replace, options[:upsert_fields] || []} + case query_with_atomics( + resource, + query, + filters, + atomics, + %{}, + upsert_set + ) do + :empty -> + :nothing + + {:ok, query} -> + query + + {:error, error} -> + raise Ash.Error.to_ash_error(error) end opts @@ -1191,8 +1219,6 @@ defmodule AshPostgres.DataLayer do opts end - changesets = Enum.to_list(stream) - ecto_changesets = Enum.map(changesets, & &1.attributes) source = @@ -1242,6 +1268,55 @@ defmodule AshPostgres.DataLayer do ) end + defp upsert_set(resource, changesets, options) do + attributes_changing_anywhere = + changesets |> Enum.flat_map(&Map.keys(&1.attributes)) |> Enum.uniq() + + update_defaults = update_defaults(resource) + # We can't reference EXCLUDED if at least one of the changesets in the stream is not + # changing the value (and we wouldn't want to even if we could as it would be unnecessary) + + upsert_fields = + (options[:upsert_fields] || []) |> Enum.filter(&(&1 in attributes_changing_anywhere)) + + fields_to_upsert = + (upsert_fields ++ Keyword.keys(update_defaults)) -- + Keyword.keys(Enum.at(changesets, 0).atomics) + + Enum.map(fields_to_upsert, fn upsert_field -> + # for safety, we check once more at the end that all values in + # upsert_fields are names of attributes. This is because + # below we use `literal/1` to bring them into the query + if is_nil(resource.__schema__(:type, upsert_field)) do + raise "Only attribute names can be used in upsert_fields" + end + + case Keyword.fetch(update_defaults, upsert_field) do + {:ok, default} -> + if upsert_field in upsert_fields do + {upsert_field, + Ecto.Query.dynamic( + [], + fragment( + "COALESCE(EXCLUDED.?, ?)", + literal(^to_string(upsert_field)), + ^default + ) + )} + else + {upsert_field, default} + end + + :error -> + {upsert_field, + Ecto.Query.dynamic( + [], + fragment("EXCLUDED.?", literal(^to_string(upsert_field))) + )} + end + end) + end + @impl true def create(resource, changeset) do changeset = %{ @@ -1486,7 +1561,7 @@ defmodule AshPostgres.DataLayer do end defp handle_raised_error(error, stacktrace, _ecto_changeset, _resource) do - {:error, Ash.Error.to_ash_error(error, stacktrace)} + reraise Ash.Error.to_error_class(error, stacktrace), stacktrace end defp constraints_to_errors(%{constraints: user_constraints} = changeset, action, constraints) do @@ -1773,37 +1848,20 @@ defmodule AshPostgres.DataLayer do if AshPostgres.DataLayer.Info.manage_tenant_update?(resource) do {:error, "Cannot currently upsert a resource that owns a tenant"} else - keys = keys || Ash.Resource.Info.primary_key(resource) + keys = keys || Ash.Resource.Info.primary_key(keys) explicitly_changing_attributes = - Enum.map( - Map.keys(changeset.attributes) -- Map.get(changeset, :defaults, []) -- keys, - fn key -> - {key, Ash.Changeset.get_attribute(changeset, key)} - end - ) - - on_conflict = - changeset - |> update_defaults() - |> Keyword.merge(explicitly_changing_attributes) + Map.keys(changeset.attributes) -- Map.get(changeset, :defaults, []) -- keys - on_conflict = - if changeset.context[:private][:upsert_fields] do - Keyword.take( - on_conflict, - changeset.context[:private][:upsert_fields] - ) - else - on_conflict - end + upsert_fields = + changeset.context[:private][:upsert_fields] || explicitly_changing_attributes case bulk_create(resource, [changeset], %{ single?: true, upsert?: true, tenant: changeset.tenant, upsert_keys: keys, - upsert_set: on_conflict, + upsert_fields: upsert_fields, return_records?: true }) do {:ok, [result]} -> @@ -1834,9 +1892,9 @@ defmodule AshPostgres.DataLayer do end end - defp update_defaults(changeset) do + defp update_defaults(resource) do attributes = - changeset.resource + resource |> Ash.Resource.Info.attributes() |> Enum.reject(&is_nil(&1.update_default)) @@ -1910,64 +1968,126 @@ defmodule AshPostgres.DataLayer do |> default_bindings(resource, changeset.context) |> Ecto.Query.select(^select) - query = - Enum.reduce(ecto_changeset.filters, query, fn {key, value}, query -> - from(row in query, - where: field(row, ^key) == ^value - ) - end) - - atomics_result = - Enum.reduce_while(changeset.atomics, {:ok, query, []}, fn {field, expr}, - {:ok, query, set} -> - used_calculations = - Ash.Filter.used_calculations( - expr, - resource + case query_with_atomics( + resource, + query, + ecto_changeset.filters, + changeset.atomics, + ecto_changeset.changes, + [] + ) do + :empty -> + {:ok, changeset.data} + + {:ok, query} -> + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + + repo_opts = + Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + + result = + dynamic_repo(resource, changeset).update_all( + query, + [], + repo_opts ) - used_aggregates = - expr - |> AshPostgres.Aggregate.used_aggregates( - resource, - used_calculations, - [] - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) - - with {:ok, query} <- - AshPostgres.Join.join_all_relationships( - query, - %Ash.Filter{ - resource: resource, - expression: expr - }, - left_only?: true - ), - {:ok, query} <- - AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0), - dynamic <- - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__) do - {:cont, {:ok, query, Keyword.put(set, field, dynamic)}} - else - other -> - {:halt, other} + case result do + {0, []} -> + {:error, + Ash.Error.Changes.StaleRecord.exception( + resource: resource, + filters: ecto_changeset.filters + )} + + {1, [result]} -> + record = + changeset.data + |> Map.merge(changeset.attributes) + |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) + + maybe_update_tenant(resource, changeset, record) + + {:ok, record} end - end) - case atomics_result do - {:ok, query, dynamics} -> - {params, set, count} = - ecto_changeset.changes - |> Map.to_list() - |> Enum.reduce({[], [], 0}, fn {key, value}, {params, set, count} -> - {[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} - end) - - {params, set, _} = - Enum.reduce(dynamics, {params, set, count}, fn {key, value}, {params, set, count} -> + {:error, error} -> + {:error, error} + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + end + end + + defp query_with_atomics( + resource, + query, + filters, + atomics, + updating_one_changes, + existing_set + ) do + query = + Enum.reduce(filters, query, fn {key, value}, query -> + from(row in query, + where: field(row, ^key) == ^value + ) + end) + + atomics_result = + Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> + used_calculations = + Ash.Filter.used_calculations( + expr, + resource + ) + + used_aggregates = + expr + |> AshPostgres.Aggregate.used_aggregates( + resource, + used_calculations, + [] + ) + |> Enum.map(fn aggregate -> + %{aggregate | load: aggregate.name} + end) + + with {:ok, query} <- + AshPostgres.Join.join_all_relationships( + query, + %Ash.Filter{ + resource: resource, + expression: expr + }, + left_only?: true + ), + {:ok, query} <- + AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0), + dynamic <- + AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__) do + {:cont, {:ok, query, Keyword.put(set, field, dynamic)}} + else + other -> + {:halt, other} + end + end) + + case atomics_result do + {:ok, query, dynamics} -> + {params, set, count} = + updating_one_changes + |> Map.to_list() + |> Enum.reduce({[], [], 0}, fn {key, value}, {params, set, count} -> + {[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} + end) + + {params, set, _} = + Enum.reduce( + dynamics ++ existing_set, + {params, set, count}, + fn {key, value}, {params, set, count} -> case AshPostgres.Expr.dynamic_expr(query, value, query.__ash_bindings__) do %Ecto.Query.DynamicExpr{} = dynamic -> result = @@ -1990,61 +2110,27 @@ defmodule AshPostgres.DataLayer do other -> {[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} end - end) - - case set do - [] -> - {:ok, changeset.data} - - set -> - query = - Map.put(query, :updates, [ - %Ecto.Query.QueryExpr{ - # why do I have to reverse the `set`??? - # it breaks if I don't - expr: [set: Enum.reverse(set)], - params: Enum.reverse(params) - } - ]) - - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - - repo_opts = - Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - - result = - dynamic_repo(resource, changeset).update_all( - query, - [], - repo_opts - ) - - case result do - {0, []} -> - {:error, - Ash.Error.Changes.StaleRecord.exception( - resource: resource, - filters: ecto_changeset.filters - )} - - {1, [result]} -> - record = - changeset.data - |> Map.merge(changeset.attributes) - |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) + end + ) - maybe_update_tenant(resource, changeset, record) + case set do + [] -> + :empty - {:ok, record} - end - end + set -> + {:ok, + Map.put(query, :updates, [ + %Ecto.Query.QueryExpr{ + # why do I have to reverse the `set`??? + # it breaks if I don't + expr: [set: Enum.reverse(set)], + params: Enum.reverse(params) + } + ])} + end - {:error, error} -> - {:error, error} - end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + {:error, error} -> + {:error, error} end end diff --git a/mix.exs b/mix.exs index c7ef419f..27bcd4ec 100644 --- a/mix.exs +++ b/mix.exs @@ -202,7 +202,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:spark, "~> 1.1"}, - {:ash, ash_version("~> 2.15 and >= 2.15.12")}, + {:ash, ash_version("~> 2.15 and >= 2.15.14")}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 1b1b20fa..abd33079 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.15.12", "03da15f8c597dea4cce550e580feb19f3eb8a93ebf44d6b3b2c797d288d4a593", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9c240f7f80b508cd5d14e7e9d6000fa01800bc397247e3467dde8fc6cae0ac23"}, + "ash": {:hex, :ash, "2.15.15", "8649aad00ba93a6e8792889f27f36954376745dde600c739bc180054d6a76469", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4da1105adcc4991841889a238ea7475f74923c36d465f886db862333cb54ecb0"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -32,10 +32,10 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, - "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, + "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.43", "5817cefa41c6f7105989fa40c044c05bf2cab7b81c8ecbd963bdbdf6eeabc85a", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "29e42b900f3a7666e67fba270ff10d7b9fc693c8c2179b6bd65aa6b8426d30ca"}, + "spark": {:hex, :spark, "1.1.44", "be9f2669b03ae43447bda77045598a4500988538a7d0ba576b8e306332822147", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e49bf5ca770cb0bb9cac7ed8da5eb7871156b3236c8c535f3f4caa93377059a3"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index e2eb7bc1..99fe0596 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -4,6 +4,22 @@ defmodule AshPostgres.AtomicsTest do import Ash.Expr + test "atomics work on upserts" do + id = Ash.UUID.generate() + + Post + |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) + |> Ash.Changeset.atomic_update(:price, expr(price + 1)) + |> Api.create!() + + Post + |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) + |> Ash.Changeset.atomic_update(:price, expr(price + 1)) + |> Api.create!() + + assert [%{price: 2}] = Post |> Api.read!() + end + test "a basic atomic works" do post = Post @@ -17,6 +33,21 @@ defmodule AshPostgres.AtomicsTest do |> Api.update!() end + test "an atomic works with a datetime" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Api.create!() + + now = DateTime.utc_now() + + assert %{created_at: ^now} = + post + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.atomic_update(:created_at, expr(^now)) + |> Api.update!() + end + test "an atomic that violates a constraint will return the proper error" do post = Post diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index 22586eb2..e5b301dd 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -53,6 +53,7 @@ defmodule AshPostgres.BulkCreateTest do upsert_identity: :uniq_one_and_two, upsert_fields: [:price], return_stream?: true, + return_errors?: true, return_records?: true ) |> Enum.sort_by(fn From 2fa7fea80c8cc7c6df5a162017b9133e6dd2839c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 11 Oct 2023 15:28:14 -0400 Subject: [PATCH 0057/1215] chore: release version v1.3.55 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62f7881..dc6d5013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.55](https://github.com/ash-project/ash_postgres/compare/v1.3.54...v1.3.55) (2023-10-11) + + + + +### Improvements: + +* support atomics on upserts + ## [v1.3.54](https://github.com/ash-project/ash_postgres/compare/v1.3.53...v1.3.54) (2023-10-10) diff --git a/mix.exs b/mix.exs index 27bcd4ec..9a207170 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.54" + @version "1.3.55" def project do [ From cd6a7cae695d714a681c2b612cd9d0625cede556 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 11 Oct 2023 16:26:48 -0400 Subject: [PATCH 0058/1215] fix: don't raise all errors --- .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 122 +++++++++++++----- lib/data_layer.ex | 2 +- 2 files changed, 88 insertions(+), 36 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index 4652e444..736f48f2 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -401,8 +401,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" - -### Options +### Arguments @@ -416,7 +415,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" - + +
- + fields @@ -434,9 +433,23 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
+### Options + + + + + + + + + + + +
NameTypeDefaultDocs
- + name @@ -456,7 +469,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + unique @@ -476,7 +489,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + concurrently @@ -496,7 +509,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + using @@ -516,7 +529,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + prefix @@ -536,7 +549,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + where @@ -556,7 +569,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + message @@ -576,7 +589,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE"
- + include @@ -661,8 +674,7 @@ end - -### Options +### Arguments @@ -676,7 +688,7 @@ end - + +
- + name @@ -696,9 +708,23 @@ end
+### Options + + + + + + + + + + + +
NameTypeDefaultDocs
- + up @@ -720,7 +746,7 @@ end
- + down @@ -741,7 +767,7 @@ end
- + code? @@ -994,8 +1020,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post - -### Options +### Arguments @@ -1009,7 +1034,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post - + +
- + relationship @@ -1028,9 +1053,23 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post
+### Options + + + + + + + + + + + +
NameTypeDefaultDocs
- + ignore? @@ -1050,7 +1089,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post
- + on_delete @@ -1071,7 +1110,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post
- + on_update @@ -1092,7 +1131,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post
- + deferrable @@ -1113,7 +1152,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post
- + name @@ -1189,8 +1228,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: - -### Options +### Arguments @@ -1204,7 +1242,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: - + +
- + attribute @@ -1225,7 +1263,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message:
- + name @@ -1244,9 +1282,23 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message:
+### Options + + + + + + + + + + + +
NameTypeDefaultDocs
- + message @@ -1266,7 +1318,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message:
- + check diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 7e515784..b7863374 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1561,7 +1561,7 @@ defmodule AshPostgres.DataLayer do end defp handle_raised_error(error, stacktrace, _ecto_changeset, _resource) do - reraise Ash.Error.to_error_class(error, stacktrace), stacktrace + {:error, Ash.Error.to_ash_error(error, stacktrace)} end defp constraints_to_errors(%{constraints: user_constraints} = changeset, action, constraints) do From 41c34b8777777a116ea87fe93d3b5603ef9ac030 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 11 Oct 2023 16:28:47 -0400 Subject: [PATCH 0059/1215] chore: release version v1.3.56 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc6d5013..ab4e22fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.56](https://github.com/ash-project/ash_postgres/compare/v1.3.55...v1.3.56) (2023-10-11) + + + + +### Bug Fixes: + +* don't raise all errors + ## [v1.3.55](https://github.com/ash-project/ash_postgres/compare/v1.3.54...v1.3.55) (2023-10-11) diff --git a/mix.exs b/mix.exs index 9a207170..9f3412f0 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.55" + @version "1.3.56" def project do [ From 0325196a208ec115eb0df01fad74d56c9db4ce49 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Oct 2023 22:47:11 -0400 Subject: [PATCH 0060/1215] improvement: allow for combining `AshPostgres.Repo` with other repos fixes #172 --- lib/data_layer.ex | 3 +-- lib/repo.ex | 15 +++++++++++---- lib/transformers/verify_repo.ex | 22 ---------------------- 3 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 lib/transformers/verify_repo.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b7863374..2c2e0662 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -391,7 +391,6 @@ defmodule AshPostgres.DataLayer do sections: @sections, transformers: [ AshPostgres.Transformers.ValidateReferences, - AshPostgres.Transformers.VerifyRepo, AshPostgres.Transformers.EnsureTableOrPolymorphic, AshPostgres.Transformers.PreventMultidimensionalArrayAggregates ] @@ -502,7 +501,7 @@ defmodule AshPostgres.DataLayer do def can?(_, :timeout), do: true def can?(_, {:filter_expr, _}), do: true def can?(_, :nested_expressions), do: true - def can?(_, {:query_aggregate, :count}), do: true + def can?(_, {:query_aggregate, _}), do: true def can?(_, :sort), do: true def can?(_, :distinct_sort), do: true def can?(_, :distinct), do: true diff --git a/lib/repo.ex b/lib/repo.ex index dd11d175..2b01a94e 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -45,6 +45,11 @@ defmodule AshPostgres.Repo do Use this to inform the data layer about the oldest potential postgres version it will be run on. Must be an integer greater than or equal to 13. + + ## Combining with other tools + + For things like `Fly.Repo`, where you might need to have more fine grained control over the repo module, + you can use the `define_ecto_repo?: false` option to `use AshPostgres.Repo`. """ @callback min_pg_version() :: integer() @@ -63,11 +68,13 @@ defmodule AshPostgres.Repo do defmacro __using__(opts) do quote bind_quoted: [opts: opts] do - otp_app = opts[:otp_app] || raise("Must configure OTP app") + if Keyword.get(opts, :define_ecto_repo?, true) do + otp_app = opts[:otp_app] || raise("Must configure OTP app") - use Ecto.Repo, - adapter: Ecto.Adapters.Postgres, - otp_app: otp_app + use Ecto.Repo, + adapter: Ecto.Adapters.Postgres, + otp_app: otp_app + end @behaviour AshPostgres.Repo diff --git a/lib/transformers/verify_repo.ex b/lib/transformers/verify_repo.ex deleted file mode 100644 index 89b53a35..00000000 --- a/lib/transformers/verify_repo.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule AshPostgres.Transformers.VerifyRepo do - @moduledoc false - use Spark.Dsl.Transformer - alias Spark.Dsl.Transformer - - def after_compile?, do: true - - def transform(dsl) do - repo = Transformer.get_option(dsl, [:postgres], :repo) - - cond do - match?({:error, _}, Code.ensure_compiled(repo)) -> - {:error, "Could not find repo module #{repo}"} - - repo.__adapter__() != Ecto.Adapters.Postgres -> - {:error, "Expected a repo using the postgres adapter `Ecto.Adapters.Postgres`"} - - true -> - {:ok, dsl} - end - end -end From d0118572f68f7424e83c17e8530c1b3195d80282 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 17 Oct 2023 11:47:42 -0400 Subject: [PATCH 0061/1215] chore: add a benchmark for bulk creates --- benchmarks/bulk_create.exs | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 benchmarks/bulk_create.exs diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs new file mode 100644 index 00000000..d3806473 --- /dev/null +++ b/benchmarks/bulk_create.exs @@ -0,0 +1,70 @@ +alias AshPostgres.Test.{Api, Post} + +ten_rows = + 1..10 + |> Enum.map(fn i -> + %{ + title: "Title: #{i}" + } + end) + +thousand_rows = + 1..1000 + |> Enum.map(fn i -> + %{ + title: "Title: #{i}" + } + end) + +hundred_thousand_rows = + 1..1000 + |> Enum.map(fn i -> + %{ + title: "Title: #{i}" + } + end) + +Api.bulk_create(ten_rows, Post, :create, + batch_size: 10, + max_concurrency: 0 +) + +AshPostgres.TestRepo.insert_all(Post, ten_rows) + +Benchee.run( + %{ + "ash sync": fn input -> + %{error_count: 0} = Api.bulk_create(input, Post, :create, + batch_size: 200, + transaction: false + ) + end, + "ecto sync": fn input -> + input + |> Stream.chunk_every(200) + |> Enum.each(fn batch -> + AshPostgres.TestRepo.insert_all(Post, batch) + end) + end, + "ash async": fn input -> + %{error_count: 0} = Api.bulk_create(input, Post, :create, + batch_size: 200, + max_concurrency: 8, + transaction: false + ) + end, + "ecto async": fn input -> + input + |> Stream.chunk_every(200) + |> Task.async_stream(fn batch -> + AshPostgres.TestRepo.insert_all(Post, batch) + end, max_concurrency: 8, timeout: :infinity) + |> Stream.run() + end + }, + inputs: %{ + "10 rows" => ten_rows, + "1000 rows" => thousand_rows, + "100000 rows" => hundred_thousand_rows + } +) From 2eb7f34e9fbff014814ad78e3981e3300837ae73 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 17 Oct 2023 12:25:38 -0400 Subject: [PATCH 0062/1215] chore: update deps --- mix.exs | 9 ++++++--- mix.lock | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 9f3412f0..96e9ea64 100644 --- a/mix.exs +++ b/mix.exs @@ -44,7 +44,10 @@ defmodule AshPostgres.MixProject do if Mix.env() == :test do def application() do - [applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex], mod: {AshPostgres.TestApp, []}] + [ + applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee], + mod: {AshPostgres.TestApp, []} + ] end end @@ -201,8 +204,8 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:spark, "~> 1.1"}, - {:ash, ash_version("~> 2.15 and >= 2.15.14")}, + {:ash, ash_version("~> 2.15 and >= 2.15.18")}, + {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index abd33079..17ea308a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,16 +1,19 @@ %{ - "ash": {:hex, :ash, "2.15.15", "8649aad00ba93a6e8792889f27f36954376745dde600c739bc180054d6a76469", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4da1105adcc4991841889a238ea7475f74923c36d465f886db862333cb54ecb0"}, + "ash": {:hex, :ash, "2.15.18", "093ed9c4db868657104e8cee2ebb0882b514ed7d97d3495e8e15eac15bca85fe", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b20789006b605c0e25f019edaa4ade82fbaf6e71ba8764e21a0c647f9a55a022"}, + "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"}, "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, + "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -35,8 +38,9 @@ "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.44", "be9f2669b03ae43447bda77045598a4500988538a7d0ba576b8e306332822147", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e49bf5ca770cb0bb9cac7ed8da5eb7871156b3236c8c535f3f4caa93377059a3"}, + "spark": {:hex, :spark, "1.1.47", "2bc334e542f519709e57853a425aa9304223c61e3ecf130b1cc014c6a4451f9a", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "c1ae778a3779b5d3e5b7c885cc9826577816dca10bbf4c21638198a1262f3df1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, From efcac93d1c3140cd769bc971523e8151f392dd08 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 17 Oct 2023 12:26:49 -0400 Subject: [PATCH 0063/1215] chore: release version v1.3.57 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4e22fc..9ed6f11f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.57](https://github.com/ash-project/ash_postgres/compare/v1.3.56...v1.3.57) (2023-10-17) + + + + +### Improvements: + +* allow for combining `AshPostgres.Repo` with other repos + ## [v1.3.56](https://github.com/ash-project/ash_postgres/compare/v1.3.55...v1.3.56) (2023-10-11) diff --git a/mix.exs b/mix.exs index 96e9ea64..23bef2d3 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.56" + @version "1.3.57" def project do [ From 112e1fe175275dddb3874f449dbe43904db61f14 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Oct 2023 16:41:23 -0400 Subject: [PATCH 0064/1215] chore: update spark/ash and add benchmark --- benchmarks/bulk_create.exs | 69 +++++++++++++++++++++++++++++++------- mix.exs | 2 +- mix.lock | 6 ++-- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs index d3806473..787d288a 100644 --- a/benchmarks/bulk_create.exs +++ b/benchmarks/bulk_create.exs @@ -24,47 +24,90 @@ hundred_thousand_rows = } end) +# do them both once to warm things up Api.bulk_create(ten_rows, Post, :create, batch_size: 10, - max_concurrency: 0 + max_concurrency: 2 ) +# do them both once to warm things up AshPostgres.TestRepo.insert_all(Post, ten_rows) +max_concurrency = 16 +batch_size = 200 + Benchee.run( %{ "ash sync": fn input -> %{error_count: 0} = Api.bulk_create(input, Post, :create, - batch_size: 200, + batch_size: batch_size, transaction: false ) end, + "ash sync assuming casted": fn input -> + %{error_count: 0} = Api.bulk_create(input, Post, :create, + batch_size: batch_size, + transaction: false, + assume_casted?: true + ) + end, "ecto sync": fn input -> input - |> Stream.chunk_every(200) + |> Stream.chunk_every(batch_size) |> Enum.each(fn batch -> AshPostgres.TestRepo.insert_all(Post, batch) end) end, - "ash async": fn input -> + "ash async stream": fn input -> + input + |> Stream.chunk_every(batch_size) + |> Task.async_stream(fn batch -> + %{error_count: 0} = Api.bulk_create(batch, Post, :create, + transaction: false + ) + end, max_concurrency: max_concurrency, timeout: :infinity) + |> Stream.run() + end, + "ash async stream assuming casted": fn input -> + input + |> Stream.chunk_every(batch_size) + |> Task.async_stream(fn batch -> + %{error_count: 0} = Api.bulk_create(batch, Post, :create, + transaction: false, + assume_casted?: true + ) + end, max_concurrency: max_concurrency, timeout: :infinity) + |> Stream.run() + end, + "ash using own async option": fn input -> %{error_count: 0} = Api.bulk_create(input, Post, :create, - batch_size: 200, - max_concurrency: 8, - transaction: false + transaction: false, + max_concurrency: max_concurrency, + batch_size: batch_size + ) + end, + "ash using own async option assuming casted": fn input -> + %{error_count: 0} = Api.bulk_create(input, Post, :create, + transaction: false, + assume_casted?: true, + max_concurrency: max_concurrency, + batch_size: batch_size ) end, - "ecto async": fn input -> + "ecto async stream": fn input -> input - |> Stream.chunk_every(200) + |> Stream.chunk_every(batch_size) |> Task.async_stream(fn batch -> AshPostgres.TestRepo.insert_all(Post, batch) - end, max_concurrency: 8, timeout: :infinity) + end, max_concurrency: max_concurrency, timeout: :infinity) |> Stream.run() end }, + after_scenario: fn _ -> + AshPostgres.TestRepo.query!("TRUNCATE posts CASCADE") + end, inputs: %{ - "10 rows" => ten_rows, - "1000 rows" => thousand_rows, - "100000 rows" => hundred_thousand_rows + # "10 rows" => ten_rows, + "1000 rows" => thousand_rows } ) diff --git a/mix.exs b/mix.exs index 23bef2d3..0010dc97 100644 --- a/mix.exs +++ b/mix.exs @@ -38,7 +38,7 @@ defmodule AshPostgres.MixProject do package: package(), source_url: "https://github.com/ash-project/ash_postgres/", homepage_url: "https://ash-hq.org/", - consolidate_protocols: Mix.env() != :test + consolidate_protocols: true ] end diff --git a/mix.lock b/mix.lock index 17ea308a..89d0eb0f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.15.18", "093ed9c4db868657104e8cee2ebb0882b514ed7d97d3495e8e15eac15bca85fe", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b20789006b605c0e25f019edaa4ade82fbaf6e71ba8764e21a0c647f9a55a022"}, + "ash": {:hex, :ash, "2.15.18", "abc793ce246cde5b68a881a4fb74237498f375dd146bb997a03bd91b96e1900f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cb33816af2b924db832ee36aefc7d17019bd822793ece44f7356780496a72dd7"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, @@ -13,6 +13,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, + "eflambe": {:hex, :eflambe, "0.2.1", "4214ecafa262863056321da59fc6efb72509f35e8dba68bf7942ddf68562e444", [:rebar3], [{:meck, "0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "617c5f2fe84447b664c136e64de07290f86c11421c0068b8c8725c82939fbf78"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, @@ -29,6 +30,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, @@ -38,7 +40,7 @@ "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.47", "2bc334e542f519709e57853a425aa9304223c61e3ecf130b1cc014c6a4451f9a", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "c1ae778a3779b5d3e5b7c885cc9826577816dca10bbf4c21638198a1262f3df1"}, + "spark": {:hex, :spark, "1.1.48", "64b804711818526e371d12ea3acc886365b14239565e361001aad801a38bad85", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "3215a8b1bb1dc93945ce9a0f68430d7265ea596c6b911f7bd6dba77b65cee370"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From 920bf6a89cf522a3aab999c3353bd0d59f176c09 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Oct 2023 16:56:09 -0400 Subject: [PATCH 0065/1215] chore: update benchmark --- benchmarks/bulk_create.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs index 787d288a..e8f20296 100644 --- a/benchmarks/bulk_create.exs +++ b/benchmarks/bulk_create.exs @@ -107,7 +107,7 @@ Benchee.run( AshPostgres.TestRepo.query!("TRUNCATE posts CASCADE") end, inputs: %{ - # "10 rows" => ten_rows, + "10 rows" => ten_rows, "1000 rows" => thousand_rows } ) From 52302046e1b7643bf4c360ae640636db83efa5fe Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Oct 2023 12:25:23 -0400 Subject: [PATCH 0066/1215] fix: properly join to related references in relationship filters --- lib/join.ex | 63 ++++++++++++++++++++++++++++++---- mix.lock | 2 +- test/filter_test.exs | 6 ++++ test/support/resources/post.ex | 4 +++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 525915ef..dda3ce05 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -46,12 +46,12 @@ defmodule AshPostgres.Join do other end) |> Ash.Filter.relationship_paths() - |> to_joins(filter) + |> to_joins(filter, query.__ash_bindings__.resource) true -> filter |> Ash.Filter.relationship_paths() - |> to_joins(filter) + |> to_joins(filter, query.__ash_bindings__.resource) end Enum.reduce_while(relationship_paths, {:ok, query}, fn @@ -128,25 +128,45 @@ defmodule AshPostgres.Join do end) end - defp to_joins(paths, filter) do + defp to_joins(paths, filter, resource) do paths |> Enum.map(fn path -> if can_inner_join?(path, filter) do {:inner, AshPostgres.Join.relationship_path_to_relationships( - filter.resource, + resource, path )} else {:left, AshPostgres.Join.relationship_path_to_relationships( - filter.resource, + resource, path )} end end) end + # defp expand_join_paths(joins) do + # Enum.flat_map(joins, fn {type, path} -> + # path + # |> sub_paths() + # |> Enum.map(&add_relationship_filter_paths/1) + # end) + # end + + # defp add_relationship_filter_paths(path) do + # last = List.last(path) + # prefix = :lists.droplast(path) + + # end + + # defp sub_paths(path) do + # Enum.map(1..Enum.count(path), fn i -> + # Enum.take(path, i) + # end) + # end + def relationship_path_to_relationships(resource, path, acc \\ []) def relationship_path_to_relationships(_resource, [], acc), do: Enum.reverse(acc) @@ -304,7 +324,6 @@ defmodule AshPostgres.Join do AshPostgres.Expr.dynamic_expr(query, filter, bindings, true) end - {:ok, query} = join_all_relationships(query, filter) from(row in query, where: ^dynamic) end @@ -609,6 +628,22 @@ defmodule AshPostgres.Join do }) |> AshPostgres.DataLayer.add_binding(binding_data) + {:ok, related_filter} = + Ash.Filter.hydrate_refs( + relationship.filter, + %{ + resource: relationship.destination, + aggregates: %{}, + calculations: %{}, + public?: false + } + ) + + related_filter = + Ash.Filter.move_to_relationship_path(related_filter, full_path) + + {:ok, query} = join_all_relationships(query, related_filter) + used_calculations = Ash.Filter.used_calculations( filter, @@ -745,6 +780,22 @@ defmodule AshPostgres.Join do query = AshPostgres.DataLayer.add_binding(query, binding_data) + {:ok, related_filter} = + Ash.Filter.hydrate_refs( + relationship.filter, + %{ + resource: relationship.destination, + aggregates: %{}, + calculations: %{}, + public?: false + } + ) + + related_filter = + Ash.Filter.move_to_relationship_path(related_filter, full_path) + + {:ok, query} = join_all_relationships(query, related_filter) + used_calculations = Ash.Filter.used_calculations( filter, diff --git a/mix.lock b/mix.lock index 89d0eb0f..d15eae8a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.15.18", "abc793ce246cde5b68a881a4fb74237498f375dd146bb997a03bd91b96e1900f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cb33816af2b924db832ee36aefc7d17019bd822793ece44f7356780496a72dd7"}, + "ash": {:hex, :ash, "2.15.20", "0702181ca817ab1cad5e3ccb53851c56a9c8ec0a010780ad7eb881a997151e38", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efa3afe081ed22f1c7d6dc45de13b8b80f8ab831ebaa255108aad11cacd23b13"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/filter_test.exs b/test/filter_test.exs index 64f62898..649f4555 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -969,4 +969,10 @@ defmodule AshPostgres.FilterTest do |> Api.read!() end end + + test "can reference related items from a relationship expression" do + Post + |> Ash.Query.filter(comments_with_high_rating.title == "foo") + |> Api.read!() + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6be836d3..4f3cc66f 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -127,6 +127,10 @@ defmodule AshPostgres.Test.Post do manual(AshPostgres.Test.Post.CommentsContainingTitle) end + has_many :comments_with_high_rating, AshPostgres.Test.Comment do + filter(expr(ratings.score > 5)) + end + has_many(:ratings, AshPostgres.Test.Rating, destination_attribute: :resource_id, relationship_context: %{data_layer: %{table: "post_ratings"}} From a1e331fa6835341704f8afc1aa609f4508d66e33 Mon Sep 17 00:00:00 2001 From: Bryan Bryce Date: Tue, 24 Oct 2023 09:33:17 -0700 Subject: [PATCH 0067/1215] docs: clean up migrations_and_tasks.md (#174) --- documentation/topics/migrations_and_tasks.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/topics/migrations_and_tasks.md b/documentation/topics/migrations_and_tasks.md index 3d21e88e..6afdace8 100644 --- a/documentation/topics/migrations_and_tasks.md +++ b/documentation/topics/migrations_and_tasks.md @@ -74,9 +74,9 @@ Define a module similar to the following: ```elixir defmodule MyApp.Release do @moduledoc """ - Houses tasks that need to be executed in the released application (because mix is not present in releases). +Tasks that need to be executed in the released application (because mix is not present in releases). """ - @app :my_ap + @app :my_app def migrate do load_app() @@ -150,13 +150,13 @@ defmodule MyApp.Release do |> Enum.flat_map(fn api -> api |> Ash.Api.Info.resources() - |> Enum.map(&AshPostgres.repo/1) + |> Enum.map(&AshPostgres.DataLayer.Info.repo/1) end) |> Enum.uniq() end defp apis do - Application.fetch_env!(:my_app, :ash_apis) + Application.fetch_env!(@app, :ash_apis) end defp load_app do From 77d035b3262f211456cca47ce0476d54d49c0137 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Oct 2023 12:39:50 -0400 Subject: [PATCH 0068/1215] chore: unlock unused deps --- mix.lock | 3 --- 1 file changed, 3 deletions(-) diff --git a/mix.lock b/mix.lock index d15eae8a..eb9b2946 100644 --- a/mix.lock +++ b/mix.lock @@ -13,8 +13,6 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, - "eflambe": {:hex, :eflambe, "0.2.1", "4214ecafa262863056321da59fc6efb72509f35e8dba68bf7942ddf68562e444", [:rebar3], [{:meck, "0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "617c5f2fe84447b664c136e64de07290f86c11421c0068b8c8725c82939fbf78"}, - "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -30,7 +28,6 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, From 53bb5941e82485b06910e118319764f7bdba1e18 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Oct 2023 12:54:31 -0400 Subject: [PATCH 0069/1215] fix: don't traverse new types for storage type --- lib/migration_generator/migration_generator.ex | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index a15a058f..10e40f07 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2732,14 +2732,7 @@ defmodule AshPostgres.MigrationGenerator do defp migration_type(other, constraints) do type = Ash.Type.get_type(other) - if Ash.Type.NewType.new_type?(type) do - migration_type( - Ash.Type.NewType.subtype_of(type), - Ash.Type.NewType.constraints(type, constraints) - ) - else - migration_type_from_storage_type(Ash.Type.storage_type(other, constraints)) - end + migration_type_from_storage_type(Ash.Type.storage_type(other, constraints)) end defp migration_type_from_storage_type(:string), do: :text From ac1673cf6959e0a48640d22a34b4c0e4d3cebaf0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Oct 2023 12:56:35 -0400 Subject: [PATCH 0070/1215] chore: release version v1.3.58 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed6f11f..3f6fbf8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.58](https://github.com/ash-project/ash_postgres/compare/v1.3.57...v1.3.58) (2023-10-24) + + + + +### Bug Fixes: + +* don't traverse new types for storage type + +* properly join to related references in relationship filters + ## [v1.3.57](https://github.com/ash-project/ash_postgres/compare/v1.3.56...v1.3.57) (2023-10-17) diff --git a/mix.exs b/mix.exs index 0010dc97..929a7b58 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.57" + @version "1.3.58" def project do [ From 9769d995b3734fad561e514bbaeec8cc05bb4526 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 25 Oct 2023 08:44:32 -0400 Subject: [PATCH 0071/1215] chore: fix unused variable --- lib/migration_generator/migration_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 10e40f07..f2843a11 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2732,7 +2732,7 @@ defmodule AshPostgres.MigrationGenerator do defp migration_type(other, constraints) do type = Ash.Type.get_type(other) - migration_type_from_storage_type(Ash.Type.storage_type(other, constraints)) + migration_type_from_storage_type(Ash.Type.storage_type(type, constraints)) end defp migration_type_from_storage_type(:string), do: :text From ad9d18aecb2a7b3c400bf9694eccde805df3fd67 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 25 Oct 2023 12:59:03 -0400 Subject: [PATCH 0072/1215] improvement: join relationships for aggregate filters --- lib/aggregate.ex | 10 ++++++++-- lib/join.ex | 24 +++++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 2413c879..b274b508 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -109,7 +109,11 @@ defmodule AshPostgres.Aggregate do first_relationship.destination, first_relationship, query, - [first_relationship.name] + [first_relationship.name], + nil, + nil, + true, + true ), agg_root_query <- Map.update!( @@ -441,7 +445,9 @@ defmodule AshPostgres.Aggregate do query, [join_relationship], nil, - subquery.__ash_bindings__.current + subquery.__ash_bindings__.current, + true, + true ) field = first_relationship.source_attribute_on_join_resource diff --git a/lib/join.ex b/lib/join.ex index dda3ce05..e725a4f7 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -183,7 +183,8 @@ defmodule AshPostgres.Join do path \\ [], bindings \\ nil, start_binding \\ nil, - is_subquery? \\ true + is_subquery? \\ true, + join_relationships? \\ false ) do resource |> Ash.Query.new(nil, base_filter?: false) @@ -203,6 +204,25 @@ defmodule AshPostgres.Join do initial_query: initial_query ) do {:ok, query} -> + query = + if join_relationships? do + {:ok, related_filter} = + Ash.Filter.hydrate_refs( + relationship.filter, + %{ + resource: relationship.destination, + public?: false + } + ) + + {:ok, query} = + AshPostgres.Join.join_all_relationships(query, related_filter) + + query + else + query + end + query = query |> do_base_filter( @@ -785,8 +805,6 @@ defmodule AshPostgres.Join do relationship.filter, %{ resource: relationship.destination, - aggregates: %{}, - calculations: %{}, public?: false } ) From 75208a70f8951dba13cd68f6dd96daff70f2c1da Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 25 Oct 2023 12:59:14 -0400 Subject: [PATCH 0073/1215] chore: release version v1.3.59 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6fbf8f..a67f8ba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.59](https://github.com/ash-project/ash_postgres/compare/v1.3.58...v1.3.59) (2023-10-25) + + + + +### Improvements: + +* join relationships for aggregate filters + ## [v1.3.58](https://github.com/ash-project/ash_postgres/compare/v1.3.57...v1.3.58) (2023-10-24) diff --git a/mix.exs b/mix.exs index 929a7b58..bf5053a1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.58" + @version "1.3.59" def project do [ From 342920a3c4b0e3f6fb426f7da29c8d681983a72a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Oct 2023 09:47:56 -0400 Subject: [PATCH 0074/1215] improvement: support `parent` in sort expressions --- lib/data_layer.ex | 12 +++++++++++- lib/expr.ex | 8 +++++--- lib/sort.ex | 19 ++++++++++++++++++- test/sort_test.exs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2c2e0662..e9446514 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -552,12 +552,22 @@ defmodule AshPostgres.DataLayer do |> default_bindings(resource, context) case context[:data_layer][:lateral_join_source] do - {_, [{%{resource: resource}, _, _, _} | _]} -> + {_, [{%{resource: resource}, _, _, _} | rest]} -> parent = resource |> resource_to_query(nil) |> default_bindings(resource, context) + parent = + case rest do + [{resource, _, _, %{name: join_relationship_name}} | _] -> + binding_data = %{type: :inner, path: [join_relationship_name], source: resource} + add_binding(parent, binding_data) + + _ -> + parent + end + ash_bindings = data_layer_query.__ash_bindings__ |> Map.put(:parent_bindings, Map.put(parent.__ash_bindings__, :parent?, true)) diff --git a/lib/expr.ex b/lib/expr.ex index cb988819..2496dae4 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1055,14 +1055,15 @@ defmodule AshPostgres.Expr do type ) do parent? = Map.get(bindings.parent_bindings, :parent_is_parent_as?, true) + new_bindings = Map.put(bindings.parent_bindings, :parent?, parent?) do_dynamic_expr( %{ query - | __ash_bindings__: Map.put(bindings.parent_bindings, :parent?, parent?) + | __ash_bindings__: new_bindings }, expr, - bindings, + new_bindings, embedded?, type ) @@ -1607,7 +1608,8 @@ defmodule AshPostgres.Expr do end end - defp set_parent_path(query, parent) do + @doc false + def set_parent_path(query, parent) do # This is a stupid name. Its actually the path we *remove* when stepping up a level. I.e the child's path Map.update!(query, :__ash_bindings__, fn ash_bindings -> ash_bindings diff --git a/lib/sort.ex b/lib/sort.ex index 08ed7a78..92112ff1 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -20,6 +20,7 @@ defmodule AshPostgres.Sort do %{ resource: resource, aggregates: %{}, + parent_stack: query.__ash_bindings__[:parent_resources] || [], calculations: %{}, public?: false } @@ -84,14 +85,30 @@ defmodule AshPostgres.Sort do |> Ash.Filter.hydrate_refs(%{ resource: resource, aggregates: query.__ash_bindings__.aggregate_defs, + parent_stack: query.__ash_bindings__[:parent_resources] || [], calculations: %{}, public?: false }) |> Ash.Filter.move_to_relationship_path(relationship_path) |> case do {:ok, expr} -> + bindings = + if query.__ash_bindings__[:parent_bindings] do + Map.update!(query.__ash_bindings__, :parent_bindings, fn parent -> + Map.put(parent, :parent_is_parent_as?, false) + end) + else + query.__ash_bindings__ + end + expr = - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false, type) + AshPostgres.Expr.dynamic_expr( + query, + expr, + bindings, + false, + type + ) {:cont, {:ok, query_expr ++ [{order, expr}]}} diff --git a/test/sort_test.exs b/test/sort_test.exs index 00a297f0..44f2ad5c 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -4,6 +4,8 @@ defmodule AshPostgres.SortTest do alias AshPostgres.Test.{Api, Comment, Post, PostLink} require Ash.Query + require Ash.Sort + import Ash.Expr test "multi-column sorts work" do Post @@ -181,4 +183,46 @@ defmodule AshPostgres.SortTest do |> Ash.Query.sort(:c_times_p) |> Api.read!() end + + test "calculations can sort on expressions" do + post1 = + Post + |> Ash.Changeset.new(%{title: "aaa", score: 0}) + |> Api.create!() + + post2 = + Post + |> Ash.Changeset.new(%{title: "bbb", score: 1}) + |> Api.create!() + + post3 = + Post + |> Ash.Changeset.new(%{title: "ccc", score: 0}) + |> Api.create!() + + PostLink + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:source_post, post1, type: :append) + |> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append) + |> Api.create!() + + PostLink + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:source_post, post2, type: :append) + |> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append) + |> Api.create!() + + PostLink + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:source_post, post3, type: :append) + |> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append) + |> Api.create!() + + posts_query = + Ash.Query.sort(Post, Ash.Sort.expr_sort(source(post_links.state))) + + Post + |> Ash.Query.load(linked_posts: posts_query) + |> Api.read!() + end end From bc4b69a5681cda56b469abaf9891499dff949c58 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 27 Oct 2023 00:06:55 -0400 Subject: [PATCH 0075/1215] chore: ensure type is compiled --- lib/migration_generator/migration_generator.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index f2843a11..74b63a53 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2848,6 +2848,8 @@ defmodule AshPostgres.MigrationGenerator do |> unwrap_type() |> Ash.Type.get_type() + type = Code.ensure_compiled!(type) + if function_exported?(type, :value_to_postgres_default, 3) do type.value_to_postgres_default(type, constraints, value) else From 0d794a3ae8e7890b71aca9d10f5610e52d6388fb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 27 Oct 2023 00:43:03 -0400 Subject: [PATCH 0076/1215] chore: release version v1.3.60 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67f8ba0..e1d714e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.60](https://github.com/ash-project/ash_postgres/compare/v1.3.59...v1.3.60) (2023-10-27) + + + + +### Improvements: + +* support `parent` in sort expressions + ## [v1.3.59](https://github.com/ash-project/ash_postgres/compare/v1.3.58...v1.3.59) (2023-10-25) diff --git a/mix.exs b/mix.exs index bf5053a1..0737e32b 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.59" + @version "1.3.60" def project do [ From 082210933f823cc0d6eff5250d8963d4dca0039b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 27 Oct 2023 10:33:03 -0400 Subject: [PATCH 0077/1215] improvement: spport `CURRENT_DATE` default --- lib/migration_generator/migration_generator.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 74b63a53..57b55ff0 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2809,6 +2809,9 @@ defmodule AshPostgres.MigrationGenerator do default == (&DateTime.utc_now/0) -> ~S[fragment("now()")] + default == (&Date.utc_today/0) -> + ~S[fragment("CURRENT_DATE")] + true -> "nil" end From cae89d856e1bbcda2e22694e324fc5be727ebc27 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Nov 2023 17:42:37 -0500 Subject: [PATCH 0078/1215] fix: get resource from proper bindings on `exists` query --- lib/expr.ex | 2 +- mix.lock | 4 ++-- test/aggregate_test.exs | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 2496dae4..b6a15ec2 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1076,7 +1076,7 @@ defmodule AshPostgres.Expr do _embedded?, _type ) do - resource = Ash.Resource.Info.related(query.__ash_bindings__.resource, at_path) + resource = Ash.Resource.Info.related(bindings.resource, at_path) first_relationship = Ash.Resource.Info.relationship(resource, first) last_relationship = diff --git a/mix.lock b/mix.lock index eb9b2946..5905963e 100644 --- a/mix.lock +++ b/mix.lock @@ -36,8 +36,8 @@ "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, - "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.48", "64b804711818526e371d12ea3acc886365b14239565e361001aad801a38bad85", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "3215a8b1bb1dc93945ce9a0f68430d7265ea596c6b911f7bd6dba77b65cee370"}, + "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, + "spark": {:hex, :spark, "1.1.50", "809da1214151ad7c592389b3ea85eb4424f727b681b90439d8ffbe5305400ce9", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ed9b1b817b52c3aaeee283032640857ee9d8398b8c4e9e7d78d77929d387b9a1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 6089c074..662d23c6 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -467,6 +467,33 @@ defmodule AshPostgres.AggregateTest do |> Api.read_one!() end + test "aggregate maintains datetime precision" do + author = + Author + |> Ash.Changeset.new(%{first_name: "first name"}) + |> Api.create!() + + post = + Post + |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + latest_comment = + Comment + |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + fetched_post = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load(:latest_comment_created_at) + |> Api.read_one!() + + assert latest_comment.created_at == fetched_post.latest_comment_created_at + end + test "it can be sorted on and produces the appropriate order" do post1 = Post From db8255e5b1d8b51085fdc7e16156a7107dac059e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 14 Nov 2023 10:56:22 -0500 Subject: [PATCH 0079/1215] improvement: support a 2 argument function for the repo option --- ....cheatmd => DSL:-AshPostgres.DataLayer.md} | 134 +++++++++--------- lib/data_layer.ex | 46 +++--- lib/data_layer/info.ex | 10 +- lib/expr.ex | 6 +- lib/join.ex | 2 +- .../migration_generator.ex | 20 +-- lib/mix/helpers.ex | 4 +- mix.exs | 2 +- mix.lock | 4 +- test/sort_test.exs | 1 - 10 files changed, 122 insertions(+), 107 deletions(-) rename documentation/dsls/{DSL:-AshPostgres.DataLayer.cheatmd => DSL:-AshPostgres.DataLayer.md} (98%) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.md similarity index 98% rename from documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd rename to documentation/dsls/DSL:-AshPostgres.DataLayer.md index 736f48f2..64982389 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -36,7 +36,7 @@ end ### Options - +
@@ -60,7 +60,7 @@ end atom
Name - + The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more @@ -74,7 +74,7 @@ end migrate? - + boolean @@ -94,7 +94,7 @@ end migration_types - + Keyword.t @@ -114,7 +114,7 @@ end migration_defaults - + Keyword.t @@ -135,13 +135,13 @@ end base_filter_sql - + String.t - + A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter @@ -155,7 +155,7 @@ end simple_join_first_aggregates - + list(atom) @@ -176,7 +176,7 @@ end skip_unique_indexes - + list(atom) | atom @@ -196,7 +196,7 @@ end unique_index_names - + list({list(atom), String.t} | {list(atom), String.t, String.t}) @@ -217,7 +217,7 @@ end exclusion_constraint_names - + `any` @@ -238,7 +238,7 @@ end identity_index_names - + `any` @@ -259,7 +259,7 @@ end foreign_key_names - + list({atom | String.t, String.t} | {atom | String.t, String.t, String.t}) @@ -280,7 +280,7 @@ end migration_ignore_attributes - + list(atom) @@ -301,13 +301,13 @@ end table - + String.t - + The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. @@ -322,13 +322,13 @@ end schema - + String.t - + The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. @@ -343,7 +343,7 @@ end polymorphic? - + boolean @@ -420,13 +420,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" fields - + list(atom | String.t) | atom | String.t - + The fields to include in the index. @@ -454,13 +454,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" name - + String.t - + the name of the index. Defaults to "#{table}_#{column}_index". @@ -474,7 +474,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" unique - + boolean @@ -494,7 +494,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" concurrently - + boolean @@ -514,13 +514,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" using - + String.t - + configures the index type. @@ -534,13 +534,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" prefix - + String.t - + specify an optional prefix for the index. @@ -554,13 +554,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" where - + String.t - + specify conditions for a partial index. @@ -574,13 +574,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" message - + String.t - + A custom message to use for unique indexes that have been violated @@ -594,13 +594,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" include - + list(String.t) - + specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. @@ -700,7 +700,7 @@ end atom - + The name of the statement, must be unique within the resource @@ -736,7 +736,7 @@ end String.t - + How to create the structure of the statement @@ -758,7 +758,7 @@ end String.t - + How to tear down the structure of the statement @@ -772,7 +772,7 @@ end code? - + boolean @@ -843,7 +843,7 @@ end list(String.t | atom) | String.t | atom - + A template that will cause the resource to create/manage the specified schema. @@ -858,7 +858,7 @@ end create? - + boolean @@ -878,7 +878,7 @@ end update? - + boolean @@ -938,13 +938,13 @@ end polymorphic_on_delete - + :delete | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. @@ -958,13 +958,13 @@ end polymorphic_on_update - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -978,13 +978,13 @@ end polymorphic_name - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -1046,7 +1046,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post atom - + The relationship to be configured @@ -1074,13 +1074,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post ignore? - + boolean - + If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way @@ -1094,13 +1094,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_delete - + :delete | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced record of the *destination* resource is deleted. @@ -1115,13 +1115,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_update - + :update | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. @@ -1136,7 +1136,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post deferrable - + false | true | :initially @@ -1157,13 +1157,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post name - + String.t - + The name of the foreign key to generate in the database. Defaults to __fkey @@ -1254,7 +1254,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: `any`
- + The attribute or list of attributes to which an error will be added if the check constraint fails @@ -1275,7 +1275,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: String.t - + The name of the constraint @@ -1303,13 +1303,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: message - + String.t - + The message to be added if the check constraint fails @@ -1323,13 +1323,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: check - + String.t - + The contents of the check. If this is set, the migration generator will include it when generating migrations @@ -1346,9 +1346,3 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: ### Introspection Target: `AshPostgres.CheckConstraint` - - - - - - diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e9446514..0482a265 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -269,10 +269,10 @@ defmodule AshPostgres.DataLayer do ], schema: [ repo: [ - type: :atom, + type: {:or, [{:behaviour, Ecto.Repo}, {:fun, 2}]}, required: true, doc: - "The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more" + "The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more. Can also be a function that takes a resource and a type `:read | :mutate` and returns the repo" ], migrate?: [ type: :boolean, @@ -447,17 +447,18 @@ defmodule AshPostgres.DataLayer do other_data_layer = Ash.DataLayer.data_layer(other_resource) data_layer == other_data_layer and - AshPostgres.DataLayer.Info.repo(resource) == AshPostgres.DataLayer.Info.repo(other_resource) + AshPostgres.DataLayer.Info.repo(resource, :read) == + AshPostgres.DataLayer.Info.repo(other_resource, :read) end def can?(resource, {:lateral_join, resources}) do - repo = AshPostgres.DataLayer.Info.repo(resource) + repo = AshPostgres.DataLayer.Info.repo(resource, :read) data_layer = Ash.DataLayer.data_layer(resource) data_layer == __MODULE__ && Enum.all?(resources, fn resource -> Ash.DataLayer.data_layer(resource) == data_layer && - AshPostgres.DataLayer.Info.repo(resource) == repo + AshPostgres.DataLayer.Info.repo(resource, :read) == repo end) end @@ -510,7 +511,7 @@ defmodule AshPostgres.DataLayer do @impl true def in_transaction?(resource) do - AshPostgres.DataLayer.Info.repo(resource).in_transaction?() + AshPostgres.DataLayer.Info.repo(resource, :mutate).in_transaction?() end @impl true @@ -683,7 +684,7 @@ defmodule AshPostgres.DataLayer do @impl true def functions(resource) do - config = AshPostgres.DataLayer.Info.repo(resource).config() + config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() functions = [ AshPostgres.Functions.Fragment, @@ -1131,7 +1132,7 @@ defmodule AshPostgres.DataLayer do @doc false def set_subquery_prefix(data_layer_query, source_query, resource) do - config = AshPostgres.DataLayer.Info.repo(resource).config() + config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() if Ash.Resource.Info.multitenancy_strategy(resource) == :context do %{ @@ -1357,7 +1358,7 @@ defmodule AshPostgres.DataLayer do AshPostgres.MultiTenancy.create_tenant!( tenant_name, - AshPostgres.DataLayer.Info.repo(resource) + AshPostgres.DataLayer.Info.repo(resource, :read) ) else :ok @@ -1378,7 +1379,7 @@ defmodule AshPostgres.DataLayer do new_tenant_name = tenant_name(resource, result) AshPostgres.MultiTenancy.rename_tenant( - AshPostgres.DataLayer.Info.repo(resource), + AshPostgres.DataLayer.Info.repo(resource, :read), old_tenant_name, new_tenant_name ) @@ -2621,7 +2622,7 @@ defmodule AshPostgres.DataLayer do repo _ -> - AshPostgres.DataLayer.Info.repo(resource) + AshPostgres.DataLayer.Info.repo(resource, :read) end func = fn -> @@ -2638,7 +2639,7 @@ defmodule AshPostgres.DataLayer do @impl true def rollback(resource, term) do - AshPostgres.DataLayer.Info.repo(resource).rollback(term) + AshPostgres.DataLayer.Info.repo(resource, :mutate).rollback(term) end defp table(resource, changeset) do @@ -2661,14 +2662,25 @@ defmodule AshPostgres.DataLayer do end defp dynamic_repo(resource, %{__ash_bindings__: %{context: %{data_layer: %{repo: repo}}}}) do - repo || AshPostgres.DataLayer.Info.repo(resource) + repo || AshPostgres.DataLayer.Info.repo(resource, :read) end - defp dynamic_repo(resource, %{context: %{data_layer: %{repo: repo}}}) do - repo || AshPostgres.DataLayer.Info.repo(resource) + defp dynamic_repo(resource, %struct{context: %{data_layer: %{repo: repo}}}) do + type = struct_to_repo_type(struct) + + repo || AshPostgres.DataLayer.Info.repo(resource, type) + end + + defp dynamic_repo(resource, %struct{}) do + AshPostgres.DataLayer.Info.repo(resource, struct_to_repo_type(struct)) end - defp dynamic_repo(resource, _) do - AshPostgres.DataLayer.Info.repo(resource) + defp struct_to_repo_type(struct) do + case struct do + Ash.Changeset -> :mutate + Ash.Query -> :read + Ecto.Query -> :read + Ecto.Changeset -> :mutate + end end end diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index fe8ae42c..1e84574b 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -4,8 +4,14 @@ defmodule AshPostgres.DataLayer.Info do alias Spark.Dsl.Extension @doc "The configured repo for a resource" - def repo(resource) do - Extension.get_opt(resource, [:postgres], :repo, nil, true) + def repo(resource, type \\ :mutate) do + case Extension.get_opt(resource, [:postgres], :repo, nil, true) do + fun when is_function(fun, 2) -> + fun.(resource, type) + + repo -> + repo + end end @doc "The configured table for a resource" diff --git a/lib/expr.ex b/lib/expr.ex index b6a15ec2..c8716935 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -251,7 +251,7 @@ defmodule AshPostgres.Expr do embedded?, type ) do - if "citext" in AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource).installed_extensions() do + if "citext" in AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate).installed_extensions() do do_dynamic_expr( query, %Fragment{ @@ -1528,7 +1528,7 @@ defmodule AshPostgres.Expr do defp require_ash_functions!(query, operator) do installed_extensions = - AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource).installed_extensions() + AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate).installed_extensions() unless "ash-functions" in installed_extensions do raise """ @@ -1540,7 +1540,7 @@ defmodule AshPostgres.Expr do end defp require_extension!(query, extension, context) do - repo = AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource) + repo = AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate) unless extension in repo.installed_extensions() do raise Ash.Error.Query.InvalidExpression, diff --git a/lib/join.ex b/lib/join.ex index e725a4f7..3598fbc6 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -386,7 +386,7 @@ defmodule AshPostgres.Join do join_query | prefix: AshPostgres.DataLayer.Info.schema(resource) || - AshPostgres.DataLayer.Info.repo(resource).config()[:default_prefix] || + AshPostgres.DataLayer.Info.repo(resource, :mutate).config()[:default_prefix] || "public" } end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 57b55ff0..c9dc2d98 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -66,7 +66,7 @@ defmodule AshPostgres.MigrationGenerator do all_resources |> Enum.filter(fn resource -> Ash.DataLayer.data_layer(resource) == AshPostgres.DataLayer && - AshPostgres.DataLayer.Info.repo(resource) == repo && + AshPostgres.DataLayer.Info.repo(resource, :mutate) == repo && (is_nil(only_resources) || resource in only_resources) end) |> Enum.flat_map(&get_snapshots(&1, all_resources)) @@ -2415,7 +2415,7 @@ defmodule AshPostgres.MigrationGenerator do defp pad(i), do: to_string(i) def get_snapshots(resource, all_resources) do - Code.ensure_compiled!(AshPostgres.DataLayer.Info.repo(resource)) + Code.ensure_compiled!(AshPostgres.DataLayer.Info.repo(resource, :mutate)) if AshPostgres.DataLayer.Info.polymorphic?(resource) do all_resources @@ -2459,7 +2459,7 @@ defmodule AshPostgres.MigrationGenerator do default( source_attribute, relationship.destination, - AshPostgres.DataLayer.Info.repo(relationship.destination) + AshPostgres.DataLayer.Info.repo(relationship.destination, :mutate) ), deferrable: false, destination_attribute_generated: source_attribute.generated?, @@ -2493,7 +2493,7 @@ defmodule AshPostgres.MigrationGenerator do check_constraints: check_constraints(resource), custom_indexes: custom_indexes(resource), custom_statements: custom_statements(resource), - repo: AshPostgres.DataLayer.Info.repo(resource), + repo: AshPostgres.DataLayer.Info.repo(resource, :mutate), multitenancy: multitenancy(resource), base_filter: AshPostgres.DataLayer.Info.base_filter_sql(resource), has_create_action: has_create_action?(resource) @@ -2584,7 +2584,7 @@ defmodule AshPostgres.MigrationGenerator do end defp attributes(resource, table) do - repo = AshPostgres.DataLayer.Info.repo(resource) + repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) ignored = AshPostgres.DataLayer.Info.migration_ignore_attributes(resource) || [] resource @@ -2680,7 +2680,7 @@ defmodule AshPostgres.MigrationGenerator do schema: relationship.context[:data_layer][:schema] || AshPostgres.DataLayer.Info.schema(relationship.destination) || - AshPostgres.DataLayer.Info.repo(relationship.destination).config()[ + AshPostgres.DataLayer.Info.repo(relationship.destination, :mutate).config()[ :default_prefix ], table: @@ -2704,7 +2704,9 @@ defmodule AshPostgres.MigrationGenerator do schema: relationship.context[:data_layer][:schema] || AshPostgres.DataLayer.Info.schema(relationship.destination) || - AshPostgres.DataLayer.Info.repo(relationship.destination).config()[:default_prefix], + AshPostgres.DataLayer.Info.repo(relationship.destination, :mutate).config()[ + :default_prefix + ], name: nil, ignore?: false }) @@ -2741,8 +2743,8 @@ defmodule AshPostgres.MigrationGenerator do defp foreign_key?(relationship) do Ash.DataLayer.data_layer(relationship.source) == AshPostgres.DataLayer && - AshPostgres.DataLayer.Info.repo(relationship.source) == - AshPostgres.DataLayer.Info.repo(relationship.destination) + AshPostgres.DataLayer.Info.repo(relationship.source, :mutate) == + AshPostgres.DataLayer.Info.repo(relationship.destination, :mutate) end defp identities(resource) do diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index ed0bd453..bc6c8bc5 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -57,7 +57,9 @@ defmodule AshPostgres.MixHelpers do end resources - |> Enum.map(&AshPostgres.DataLayer.Info.repo(&1)) + |> Enum.flat_map( + &[AshPostgres.DataLayer.Info.repo(&1, :read), AshPostgres.DataLayer.Info.repo(&1, :mutate)] + ) |> Enum.uniq() |> case do [] -> diff --git a/mix.exs b/mix.exs index 0737e32b..57b067cc 100644 --- a/mix.exs +++ b/mix.exs @@ -241,7 +241,7 @@ defmodule AshPostgres.MixProject do "sobelow --skip -i Config.Secrets --ignore-files lib/migration_generator/migration_generator.ex", credo: "credo --strict", docs: [ - "spark.cheat_sheets", + # "spark.cheat_sheets", "docs", "spark.replace_doc_links", "spark.cheat_sheets_in_search" diff --git a/mix.lock b/mix.lock index 5905963e..e6b1fdce 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.15.20", "0702181ca817ab1cad5e3ccb53851c56a9c8ec0a010780ad7eb881a997151e38", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efa3afe081ed22f1c7d6dc45de13b8b80f8ab831ebaa255108aad11cacd23b13"}, + "ash": {:hex, :ash, "2.17.1", "728a917baab9599b4bed4156009b3465fa5a24a9511dab212894b044d13d1a36", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d45d8c6a9c99a0088ec5451692e46e03556d00575bfe00497a2e32f90163763f"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, @@ -37,7 +37,7 @@ "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, - "spark": {:hex, :spark, "1.1.50", "809da1214151ad7c592389b3ea85eb4424f727b681b90439d8ffbe5305400ce9", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ed9b1b817b52c3aaeee283032640857ee9d8398b8c4e9e7d78d77929d387b9a1"}, + "spark": {:hex, :spark, "1.1.51", "8458de5abbb89d18dd5c9235dd39e3757076eba84a5078d1cdc2c1e23c39aa95", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ed8410aa8db08867b8fff3d65e54deeb7f6f6cf2b8698fc405a386c1c7a9e4f0"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, diff --git a/test/sort_test.exs b/test/sort_test.exs index 44f2ad5c..25f6a8b9 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -5,7 +5,6 @@ defmodule AshPostgres.SortTest do require Ash.Query require Ash.Sort - import Ash.Expr test "multi-column sorts work" do Post From 2b29f728e2c69ac915b085af803be6aa2be2ea2e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 14 Nov 2023 12:59:42 -0500 Subject: [PATCH 0080/1215] fix: handle additional case for new functional repo callback --- lib/transformers/prevent_multidimensional_array_aggregates.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transformers/prevent_multidimensional_array_aggregates.ex b/lib/transformers/prevent_multidimensional_array_aggregates.ex index a52ea723..396d633d 100644 --- a/lib/transformers/prevent_multidimensional_array_aggregates.ex +++ b/lib/transformers/prevent_multidimensional_array_aggregates.ex @@ -35,7 +35,7 @@ defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do end end) - repo = Transformer.get_option(dsl, [:postgres], :repo) + repo = AshPostgres.DataLayer.Info.repo(dsl, :mutate) cond do match?({:error, _}, Code.ensure_compiled(repo)) -> From ce628384d2add32ed33161743a5f2e2b0ebbe548 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 14 Nov 2023 13:59:30 -0500 Subject: [PATCH 0081/1215] fix: don't ensure repo compiled at compile time --- .../prevent_multidimensional_array_aggregates.ex | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/transformers/prevent_multidimensional_array_aggregates.ex b/lib/transformers/prevent_multidimensional_array_aggregates.ex index 396d633d..62bfa736 100644 --- a/lib/transformers/prevent_multidimensional_array_aggregates.ex +++ b/lib/transformers/prevent_multidimensional_array_aggregates.ex @@ -35,17 +35,6 @@ defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do end end) - repo = AshPostgres.DataLayer.Info.repo(dsl, :mutate) - - cond do - match?({:error, _}, Code.ensure_compiled(repo)) -> - {:error, "Could not find repo module #{repo}"} - - repo.__adapter__() != Ecto.Adapters.Postgres -> - {:error, "Expected a repo using the postgres adapter `Ecto.Adapters.Postgres`"} - - true -> - {:ok, dsl} - end + {:ok, dsl} end end From 4d672ba0885605b5bf414b07775b731d4e115083 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Nov 2023 16:08:44 -0500 Subject: [PATCH 0082/1215] fix: don't append update_defaults automatically if `upsert_fields` was set --- lib/data_layer.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 0482a265..c0290373 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1290,7 +1290,7 @@ defmodule AshPostgres.DataLayer do (options[:upsert_fields] || []) |> Enum.filter(&(&1 in attributes_changing_anywhere)) fields_to_upsert = - (upsert_fields ++ Keyword.keys(update_defaults)) -- + upsert_fields -- Keyword.keys(Enum.at(changesets, 0).atomics) Enum.map(fields_to_upsert, fn upsert_field -> @@ -1860,8 +1860,14 @@ defmodule AshPostgres.DataLayer do else keys = keys || Ash.Resource.Info.primary_key(keys) + update_defaults = update_defaults(resource) + explicitly_changing_attributes = - Map.keys(changeset.attributes) -- Map.get(changeset, :defaults, []) -- keys + changeset.attributes + |> Map.keys() + |> Enum.concat(Keyword.keys(update_defaults)) + |> Kernel.--(Map.get(changeset, :defaults, [])) + |> Kernel.--(keys) upsert_fields = changeset.context[:private][:upsert_fields] || explicitly_changing_attributes From 2d34ad1e5675702eb522706220b65174a35d43ff Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Nov 2023 17:00:07 -0500 Subject: [PATCH 0083/1215] chore: release version v1.3.61 --- CHANGELOG.md | 21 +++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d714e1..f2864752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.61](https://github.com/ash-project/ash_postgres/compare/v1.3.60...v1.3.61) (2023-11-15) + + + + +### Bug Fixes: + +* don't append update_defaults automatically if `upsert_fields` was set + +* don't ensure repo compiled at compile time + +* handle additional case for new functional repo callback + +* get resource from proper bindings on `exists` query + +### Improvements: + +* support a 2 argument function for the repo option + +* spport `CURRENT_DATE` default + ## [v1.3.60](https://github.com/ash-project/ash_postgres/compare/v1.3.59...v1.3.60) (2023-10-27) diff --git a/mix.exs b/mix.exs index 57b067cc..f2e3fba0 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.60" + @version "1.3.61" def project do [ From 4f5312a08961b4b6aff4b6998197edff5456d20b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 08:31:50 -0500 Subject: [PATCH 0084/1215] fix: add calculation context to calculation expressions --- lib/expr.ex | 18 ++++++++++++++++++ mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index c8716935..5c65b695 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -792,6 +792,15 @@ defmodule AshPostgres.Expr do } ) do {:ok, expression} -> + expression = + Ash.Actions.Read.add_calc_context_to_filter( + expression, + calculation.context[:actor], + calculation.context[:authorize?], + calculation.context[:tenant], + calculation.context[:tracer] + ) + do_dynamic_expr( query, expression, @@ -975,6 +984,15 @@ defmodule AshPostgres.Expr do } ) do {:ok, hydrated} -> + hydrated = + Ash.Actions.Read.add_calc_context_to_filter( + hydrated, + calculation.context[:actor], + calculation.context[:authorize?], + calculation.context[:tenant], + calculation.context[:tracer] + ) + expr = do_dynamic_expr( query, diff --git a/mix.exs b/mix.exs index f2e3fba0..1e0323e4 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.15 and >= 2.15.18")}, + {:ash, ash_version("~> 2.17 and >= 2.17.2")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index e6b1fdce..8414bcfc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.1", "728a917baab9599b4bed4156009b3465fa5a24a9511dab212894b044d13d1a36", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d45d8c6a9c99a0088ec5451692e46e03556d00575bfe00497a2e32f90163763f"}, + "ash": {:hex, :ash, "2.17.2", "04b7c3b6340720d525a1410c05a854461bc487a43459718f3cdd705581d48b43", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fbcf8e18cf640612bbaa356cc1749d2521211e9d116cd296ef785ab48dacf685"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From af8d8dbcac52fcfdccbf0e8dbf76659b87b8826e Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Thu, 16 Nov 2023 11:56:09 +1000 Subject: [PATCH 0085/1215] test: add failing test to demo an issue with has_one calculations --- .../20231116013020.json | 101 ++++++++++++++++++ .../20231116013020.json | 49 +++++++++ ...3020_add_complex_calculations_channels.exs | 55 ++++++++++ test/complex_calculations_test.exs | 53 +++++++++ test/support/complex_calculations/registry.ex | 2 + .../complex_calculations/resources/channel.ex | 64 +++++++++++ .../resources/channel_member.ex | 27 +++++ test/support/resources/user.ex | 1 + 8 files changed, 352 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json create mode 100644 priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs create mode 100644 test/support/complex_calculations/resources/channel.ex create mode 100644 test/support/complex_calculations/resources/channel_member.ex diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json new file mode 100644 index 00000000..bb1d226a --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json @@ -0,0 +1,101 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "complex_calculations_certifications_channel_members_user_id_fkey", + "table": "users", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "destination_attribute": "id", + "on_delete": null, + "on_update": null, + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "channel_id", + "references": { + "name": "complex_calculations_certifications_channel_members_channel_id_fkey", + "table": "complex_calculations_channels", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "destination_attribute": "id", + "on_delete": null, + "on_update": null, + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "complex_calculations_certifications_channel_members", + "hash": "D191734C635BD14D086DDF8157E1C2CF3CE536579A2B3A48ED30BF539E161AAD", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json b/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json new file mode 100644 index 00000000..1403539a --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "complex_calculations_channels", + "hash": "2C35FB16B98FA229F91F69D2D3BEEDA41BAB55896E59247A96D9068AD5BF000A", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs b/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs new file mode 100644 index 00000000..e090e448 --- /dev/null +++ b/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs @@ -0,0 +1,55 @@ +defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationsChannels 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 + create table(:complex_calculations_channels, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :created_at, :utc_datetime_usec, null: false, default: fragment("now()") + add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()") + end + + create table(:complex_calculations_certifications_channel_members, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :created_at, :utc_datetime_usec, null: false, default: fragment("now()") + add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()") + + add :user_id, + references(:users, + column: :id, + name: "complex_calculations_certifications_channel_members_user_id_fkey", + type: :uuid, + prefix: "public" + ) + + add :channel_id, + references(:complex_calculations_channels, + column: :id, + name: "complex_calculations_certifications_channel_members_channel_id_fkey", + type: :uuid, + prefix: "public" + ) + end + end + + def down do + drop constraint( + :complex_calculations_certifications_channel_members, + "complex_calculations_certifications_channel_members_user_id_fkey" + ) + + drop constraint( + :complex_calculations_certifications_channel_members, + "complex_calculations_certifications_channel_members_channel_id_fkey" + ) + + drop table(:complex_calculations_certifications_channel_members) + + drop table(:complex_calculations_channels) + end +end \ No newline at end of file diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 6c5535ce..01f35e1e 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -59,4 +59,57 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do assert certification.some_documentation_created end + + test "channel: first_member and second member" do + channel = + AshPostgres.Test.ComplexCalculations.Channel + |> Ash.Changeset.new() + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + user_1 = + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "User 1"}) + |> AshPostgres.Test.Api.create!() + + user_2 = + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "User 2"}) + |> AshPostgres.Test.Api.create!() + + channel_member_1 = + AshPostgres.Test.ComplexCalculations.ChannelMember + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:channel, channel, type: :append) + |> Ash.Changeset.manage_relationship(:user, user_1, type: :append) + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + channel_member_2 = + AshPostgres.Test.ComplexCalculations.ChannelMember + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:channel, channel, type: :append) + |> Ash.Changeset.manage_relationship(:user, user_2, type: :append) + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + channel = + channel + |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :first_member, + :second_member + ]) + + assert channel.first_member.id == channel_member_1.id + assert channel.second_member.id == channel_member_2.id + + channel = + channel + |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_1) + + assert channel.name == user_1.name + + channel = + channel + |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_2) + + assert channel.name == user_2.name + end end diff --git a/test/support/complex_calculations/registry.ex b/test/support/complex_calculations/registry.ex index eed870d7..13232670 100644 --- a/test/support/complex_calculations/registry.ex +++ b/test/support/complex_calculations/registry.ex @@ -6,5 +6,7 @@ defmodule AshPostgres.Test.ComplexCalculations.Registry do entry(AshPostgres.Test.ComplexCalculations.Certification) entry(AshPostgres.Test.ComplexCalculations.Skill) entry(AshPostgres.Test.ComplexCalculations.Documentation) + entry(AshPostgres.Test.ComplexCalculations.Channel) + entry(AshPostgres.Test.ComplexCalculations.ChannelMember) end end diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex new file mode 100644 index 00000000..997eadfb --- /dev/null +++ b/test/support/complex_calculations/resources/channel.ex @@ -0,0 +1,64 @@ +defmodule AshPostgres.Test.ComplexCalculations.Channel do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + require Ash.Expr + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + create_timestamp(:created_at, private?: false) + update_timestamp(:updated_at, private?: false) + end + + postgres do + table "complex_calculations_channels" + repo(AshPostgres.TestRepo) + end + + relationships do + has_many(:channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember) + + has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + destination_attribute(:channel_id) + from_many?(true) + sort(created_at: :asc) + end + + has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + destination_attribute(:channel_id) + from_many?(true) + sort(created_at: :desc) + end + end + + aggregates do + first(:first_member_name, [:first_member, :user], :name) + first(:second_member_name, [:second_member, :user], :name) + end + + calculations do + calculate :name, :string do + calculation( + expr( + cond do + first_member.user_id == ^actor(:id) -> + second_member_name + + second_member.user_id == ^actor(:id) -> + first_member_name + + true -> + first_member_name <> ", " <> second_member_name + end + ) + ) + end + end +end diff --git a/test/support/complex_calculations/resources/channel_member.ex b/test/support/complex_calculations/resources/channel_member.ex new file mode 100644 index 00000000..dd5a8a8b --- /dev/null +++ b/test/support/complex_calculations/resources/channel_member.ex @@ -0,0 +1,27 @@ +defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + create_timestamp(:created_at, private?: false) + update_timestamp(:updated_at, private?: false) + end + + postgres do + table "complex_calculations_certifications_channel_members" + repo(AshPostgres.TestRepo) + end + + relationships do + belongs_to(:user, AshPostgres.Test.User, api: AshPostgres.Test.Api) + belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel) + end +end diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index bdb7f7ad..31647d77 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -9,6 +9,7 @@ defmodule AshPostgres.Test.User do attributes do uuid_primary_key(:id) attribute(:is_active, :boolean) + attribute(:name, :string) end postgres do From ef96cd541e7498de587cffed94f167189790acdb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 11:02:24 -0500 Subject: [PATCH 0086/1215] fix: use `synonymous_relationship_path` when looking up ref bindings --- lib/expr.ex | 14 ++++++++++++-- lib/join.ex | 18 ++++++++++++++++++ mix.exs | 2 +- .../complex_calculations/resources/channel.ex | 4 ++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 5c65b695..ad17183d 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1492,13 +1492,23 @@ defmodule AshPostgres.Expr do defp ref_binding(%{attribute: %Ash.Resource.Attribute{}} = ref, bindings) do Enum.find_value(bindings.bindings, fn {binding, data} -> - data.path == ref.relationship_path && data.type in [:inner, :left, :root] && binding + data.type in [:inner, :left, :root] && + Ash.SatSolver.synonymous_relationship_paths?( + bindings.resource, + data.path, + ref.relationship_path + ) && binding end) end defp ref_binding(%{attribute: %Ash.Query.Aggregate{}} = ref, bindings) do Enum.find_value(bindings.bindings, fn {binding, data} -> - data.path == ref.relationship_path && data.type in [:inner, :left, :root] && binding + data.type in [:inner, :left, :root] && + Ash.SatSolver.synonymous_relationship_paths?( + bindings.resource, + data.path, + ref.relationship_path + ) && binding end) end diff --git a/lib/join.ex b/lib/join.ex index 3598fbc6..fb1662ab 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -30,6 +30,21 @@ defmodule AshPostgres.Join do relationship_paths \\ nil, path \\ [], source \\ nil + ) + + # simple optimization for common cases + def join_all_relationships(query, filter, _opts, relationship_paths, _path, _source) + when is_nil(relationship_paths) and filter in [nil, true, false] do + {:ok, query} + end + + def join_all_relationships( + query, + filter, + opts, + relationship_paths, + path, + source ) do relationship_paths = cond do @@ -49,6 +64,8 @@ defmodule AshPostgres.Join do |> to_joins(filter, query.__ash_bindings__.resource) true -> + id = System.unique_integer([:monotonic, :positive]) + filter |> Ash.Filter.relationship_paths() |> to_joins(filter, query.__ash_bindings__.resource) @@ -130,6 +147,7 @@ defmodule AshPostgres.Join do defp to_joins(paths, filter, resource) do paths + |> Enum.reject(&(&1 == [])) |> Enum.map(fn path -> if can_inner_join?(path, filter) do {:inner, diff --git a/mix.exs b/mix.exs index 1e0323e4..d3796f98 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.2")}, + {:ash, ash_version("~> 2.17 and >= 2.17.3")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex index 997eadfb..b357983c 100644 --- a/test/support/complex_calculations/resources/channel.ex +++ b/test/support/complex_calculations/resources/channel.ex @@ -49,10 +49,10 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do expr( cond do first_member.user_id == ^actor(:id) -> - second_member_name + first_member_name second_member.user_id == ^actor(:id) -> - first_member_name + second_member_name true -> first_member_name <> ", " <> second_member_name From 60e1c0ba6d9e759a965d100c1f58ccf43a7e44bc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 11:03:31 -0500 Subject: [PATCH 0087/1215] chore: release version v1.3.62 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2864752..3e832662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.62](https://github.com/ash-project/ash_postgres/compare/v1.3.61...v1.3.62) (2023-11-16) + + + + +### Bug Fixes: + +* use `synonymous_relationship_path` when looking up ref bindings + +* add calculation context to calculation expressions + ## [v1.3.61](https://github.com/ash-project/ash_postgres/compare/v1.3.60...v1.3.61) (2023-11-15) diff --git a/mix.exs b/mix.exs index d3796f98..1d3c97f2 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.61" + @version "1.3.62" def project do [ From b2d0f7e1bfa5c7667e2cfab9b76faaee60f3a7e5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 11:13:25 -0500 Subject: [PATCH 0088/1215] chore: update ash version --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8414bcfc..80212e8e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.2", "04b7c3b6340720d525a1410c05a854461bc487a43459718f3cdd705581d48b43", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fbcf8e18cf640612bbaa356cc1749d2521211e9d116cd296ef785ab48dacf685"}, + "ash": {:hex, :ash, "2.17.3", "b87448baa52360d3a8934526bb9e6c29cbbc0fa0047884a8d5616fda7ee263a4", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "03d310f44240d8c47e7b405afcc8d87b290dac702698d1b470374ba54ae45c49"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From f62eef176a407f482e1efaac90b6efa2156ec59f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 11:15:11 -0500 Subject: [PATCH 0089/1215] chore: remove testing variable --- lib/join.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index fb1662ab..52416923 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -64,8 +64,6 @@ defmodule AshPostgres.Join do |> to_joins(filter, query.__ash_bindings__.resource) true -> - id = System.unique_integer([:monotonic, :positive]) - filter |> Ash.Filter.relationship_paths() |> to_joins(filter, query.__ash_bindings__.resource) From 39754d1f385cb59c0c6fb3a23cb9e4d97d59b842 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 17:48:39 -0500 Subject: [PATCH 0090/1215] fix: handle `no_attributes?` flag on aggregates better fix: properly handle sorted relationships in aggregates --- lib/aggregate.ex | 103 +++++++++++++++++++++++++++++++++---------- lib/data_layer.ex | 39 +++++++++-------- lib/expr.ex | 2 + lib/join.ex | 109 +++++++++++++++++++++++++++++++++++----------- lib/sort.ex | 43 +++++++++++------- 5 files changed, 215 insertions(+), 81 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index b274b508..eb843cb0 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -109,6 +109,7 @@ defmodule AshPostgres.Aggregate do first_relationship.destination, first_relationship, query, + false, [first_relationship.name], nil, nil, @@ -148,7 +149,8 @@ defmodule AshPostgres.Aggregate do Ash.Resource.Info.related( first_relationship.destination, relationship_path - ) + ), + first_relationship ), query <- join_subquery( @@ -279,7 +281,10 @@ defmodule AshPostgres.Aggregate do resource, path ++ aggregate.relationship_path )} - ] + ], + [], + nil, + false ) do {:ok, query} -> binding = @@ -394,11 +399,17 @@ defmodule AshPostgres.Aggregate do field = first_relationship.destination_attribute new_subquery = - from(row in subquery, - select_merge: map(row, ^[field]), - group_by: field(row, ^first_relationship.destination_attribute), - distinct: true - ) + from(row in subquery, distinct: true) + + new_subquery = + if Map.get(first_relationship, :no_attributes?) do + new_subquery + else + from(row in new_subquery, + group_by: field(row, ^field), + select_merge: map(row, ^[field]) + ) + end {:ok, subquery} = module.ash_postgres_subquery( @@ -443,6 +454,7 @@ defmodule AshPostgres.Aggregate do join_relationship_struct.destination, join_relationship_struct, query, + false, [join_relationship], nil, subquery.__ash_bindings__.current, @@ -504,13 +516,21 @@ defmodule AshPostgres.Aggregate do subquery = from(row in subquery, - group_by: field(row, ^first_relationship.destination_attribute), - select_merge: map(row, ^[field]), where: field(parent_as(^source_binding), ^first_relationship.source_attribute) == field(as(^0), ^first_relationship.destination_attribute) ) + subquery = + if Map.get(first_relationship, :no_attributes?) do + subquery + else + from(row in subquery, + group_by: field(row, ^field), + select_merge: map(row, ^[field]) + ) + end + subquery = AshPostgres.Join.set_join_prefix(subquery, query, first_relationship.destination) query = @@ -548,9 +568,24 @@ defmodule AshPostgres.Aggregate do ) end - defp select_all_aggregates(aggregates, joined, relationship_path, _query, is_single?, resource) do + defp select_all_aggregates( + aggregates, + joined, + relationship_path, + _query, + is_single?, + resource, + first_relationship + ) do Enum.reduce(aggregates, joined, fn aggregate, joined -> - add_subquery_aggregate_select(joined, relationship_path, aggregate, resource, is_single?) + add_subquery_aggregate_select( + joined, + relationship_path, + aggregate, + resource, + is_single?, + first_relationship + ) end) end @@ -576,7 +611,8 @@ defmodule AshPostgres.Aggregate do )} ], [], - nil + nil, + false ) end end @@ -760,7 +796,8 @@ defmodule AshPostgres.Aggregate do relationship_path, %{kind: :first} = aggregate, resource, - is_single? + is_single?, + first_relationship ) do query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) @@ -782,19 +819,28 @@ defmodule AshPostgres.Aggregate do field = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + has_sort? = has_sort?(aggregate.query) + sorted = - if has_sort?(aggregate.query) do - {:ok, sort_expr} = + if has_sort? || first_relationship.sort not in [nil, []] do + {sort, binding} = + if has_sort? do + {aggregate.query.sort, binding} + else + {List.wrap(first_relationship.sort), 0} + end + + {:ok, sort_expr, query} = AshPostgres.Sort.sort( query, - aggregate.query.sort, + sort, Ash.Resource.Info.related( query.__ash_bindings__.resource, relationship_path ), relationship_path, binding, - true + :return ) question_marks = Enum.map(sort_expr, fn _ -> " ? " end) @@ -842,7 +888,8 @@ defmodule AshPostgres.Aggregate do relationship_path, %{kind: :list} = aggregate, resource, - is_single? + is_single?, + first_relationship ) do query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) @@ -863,19 +910,28 @@ defmodule AshPostgres.Aggregate do field = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + has_sort? = has_sort?(aggregate.query) + sorted = - if has_sort?(aggregate.query) do - {:ok, sort_expr} = + if has_sort? || first_relationship.sort not in [nil, []] do + {sort, binding} = + if has_sort? do + {aggregate.query.sort, binding} + else + {List.wrap(first_relationship.sort), 0} + end + + {:ok, sort_expr, query} = AshPostgres.Sort.sort( query, - aggregate.query.sort, + sort, Ash.Resource.Info.related( query.__ash_bindings__.resource, relationship_path ), relationship_path, binding, - true + :return ) question_marks = Enum.map(sort_expr, fn _ -> " ? " end) @@ -935,7 +991,8 @@ defmodule AshPostgres.Aggregate do relationship_path, %{kind: kind} = aggregate, resource, - is_single? + is_single?, + _first_relationship ) when kind in [:count, :sum, :avg, :max, :min, :custom] do query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c0290373..6037e4d9 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -741,12 +741,16 @@ defmodule AshPostgres.DataLayer do aggregates, query, fn agg, query -> + first_relationship = + Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + AshPostgres.Aggregate.add_subquery_aggregate_select( query, agg.relationship_path |> Enum.drop(1), agg, resource, - true + true, + first_relationship ) end ) @@ -826,12 +830,19 @@ defmodule AshPostgres.DataLayer do _ -> false end) + first_relationship = + Ash.Resource.Info.relationship( + source_resource, + agg.relationship_path |> Enum.at(0) + ) + AshPostgres.Aggregate.add_subquery_aggregate_select( subquery, agg.relationship_path |> Enum.drop(1), agg, destination_resource, - has_exists? + has_exists?, + first_relationship ) end ) @@ -2254,7 +2265,7 @@ defmodule AshPostgres.DataLayer do |> apply_sort( query.__ash_bindings__[:distinct_sort] || query.__ash_bindings__[:sort], resource, - true + :direct ) |> case do {:ok, distinct_query} -> @@ -2322,25 +2333,14 @@ defmodule AshPostgres.DataLayer do end end - defp apply_sort(query, sort, resource, directly? \\ false) + defp apply_sort(query, sort, resource, type \\ :window) defp apply_sort(query, sort, _resource, _) when sort in [nil, []] do {:ok, query |> set_sort_applied()} end - defp apply_sort(query, sort, resource, directly?) do - query - |> AshPostgres.Sort.sort(sort, resource, [], 0, directly?) - |> case do - {:ok, sort} when directly? -> - {:ok, query |> Ecto.Query.order_by(^sort) |> set_sort_applied()} - - {:ok, query} -> - {:ok, query |> set_sort_applied()} - - {:error, error} -> - {:error, error} - end + defp apply_sort(query, sort, resource, type) do + AshPostgres.Sort.sort(query, sort, resource, [], 0, type) end defp set_sort_applied(query) do @@ -2503,7 +2503,10 @@ defmodule AshPostgres.DataLayer do end @doc false - def default_bindings(query, resource, context \\ %{}) do + def default_bindings(query, resource, context \\ %{}) + def default_bindings(%{__ash_bindings__: _} = query, _resource, _context), do: query + + def default_bindings(query, resource, context) do start_bindings = context[:data_layer][:start_bindings_at] || 0 Map.put_new(query, :__ash_bindings__, %{ diff --git a/lib/expr.ex b/lib/expr.ex index ad17183d..9a8758ff 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1123,6 +1123,7 @@ defmodule AshPostgres.Expr do first_relationship.destination, first_relationship, query, + false, [first_relationship.name] ) @@ -1229,6 +1230,7 @@ defmodule AshPostgres.Expr do first_relationship.through, through_relationship, query, + false, [first_relationship.join_relationship], through_bindings, nil, diff --git a/lib/join.ex b/lib/join.ex index 52416923..90d809d2 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -29,11 +29,12 @@ defmodule AshPostgres.Join do opts \\ [], relationship_paths \\ nil, path \\ [], - source \\ nil + source \\ nil, + sort? \\ true ) # simple optimization for common cases - def join_all_relationships(query, filter, _opts, relationship_paths, _path, _source) + def join_all_relationships(query, filter, _opts, relationship_paths, _path, _source, _sort?) when is_nil(relationship_paths) and filter in [nil, true, false] do {:ok, query} end @@ -44,7 +45,8 @@ defmodule AshPostgres.Join do opts, relationship_paths, path, - source + source, + sort? ) do relationship_paths = cond do @@ -116,7 +118,8 @@ defmodule AshPostgres.Join do Enum.map(path, & &1.name), current_join_type, source, - filter + filter, + sort? ) do {:ok, joined_query} -> joined_query_with_distinct = add_distinct(relationship, join_type, joined_query) @@ -196,6 +199,7 @@ defmodule AshPostgres.Join do resource, relationship, root_query, + sort?, path \\ [], bindings \\ nil, start_binding \\ nil, @@ -211,10 +215,13 @@ defmodule AshPostgres.Join do %{valid?: true} = query -> ash_query = query - initial_query = %{ - AshPostgres.DataLayer.resource_to_query(resource, nil) - | prefix: Map.get(root_query, :prefix) - } + initial_query = + %{ + AshPostgres.DataLayer.resource_to_query(resource, nil) + | prefix: Map.get(root_query, :prefix) + } + + initial_query = do_relationship_sort(initial_query, relationship, sort?) case Ash.Query.data_layer_query(query, initial_query: initial_query @@ -269,6 +276,38 @@ defmodule AshPostgres.Join do end end + defp do_relationship_sort( + query, + %{destination: destination, sort: sort, from_many?: true}, + true + ) + when sort not in [nil, []] do + query = + if query.aliases[0] do + query + else + from(row in query, as: ^0) + end + + query = + case query do + %{__ash_bindings__: ash_bindings} -> + ash_bindings + + _ -> + AshPostgres.DataLayer.default_bindings(query, destination) + end + + {:ok, order_by, query} = + AshPostgres.Sort.sort(query, sort, query.__ash_bindings__.resource, [], 0, :return) + + from(row in subquery(Ecto.Query.order_by(query, ^order_by)), []) + |> AshPostgres.DataLayer.default_bindings(destination) + |> Map.update!(:__ash_bindings__, &Map.put(&1, :current, query.__ash_bindings__.current)) + end + + defp do_relationship_sort(query, _, _), do: query + defp do_relationship_filter(query, %{filter: nil}, _, _, _, _, _, _), do: query defp do_relationship_filter( @@ -500,7 +539,8 @@ defmodule AshPostgres.Join do path, join_type, source, - filter + filter, + sort? ) do case Map.get(query.__ash_bindings__.bindings, path) do %{type: existing_join_type} when join_type != existing_join_type -> @@ -513,7 +553,8 @@ defmodule AshPostgres.Join do path, join_type, source, - filter + filter, + sort? ) _ -> @@ -527,7 +568,8 @@ defmodule AshPostgres.Join do path, kind, source, - filter + filter, + sort? ) do full_path = path ++ [relationship.name] initial_ash_bindings = query.__ash_bindings__ @@ -565,6 +607,7 @@ defmodule AshPostgres.Join do relationship.destination, relationship, query, + sort?, full_path, root_bindings ) do @@ -596,13 +639,14 @@ defmodule AshPostgres.Join do end end) - relationship_destination = - case used_aggregates do - [] -> - relationship_destination + needs_subquery? = + used_aggregates != [] || Map.get(relationship, :from_many?) - _ -> - subquery(relationship_destination) + relationship_destination = + if needs_subquery? do + subquery(relationship_destination) + else + relationship_destination end case module.ash_postgres_join( @@ -642,7 +686,8 @@ defmodule AshPostgres.Join do path, kind, source, - filter + filter, + sort? ) do join_relationship = Ash.Resource.Info.relationship(relationship.source, relationship.join_relationship) @@ -710,6 +755,7 @@ defmodule AshPostgres.Join do relationship.through, join_relationship, query, + false, join_path, root_bindings ), @@ -718,6 +764,7 @@ defmodule AshPostgres.Join do relationship.destination, relationship, query, + sort?, path, root_bindings ) do @@ -750,13 +797,14 @@ defmodule AshPostgres.Join do end end) - relationship_destination = - case used_aggregates do - [] -> - relationship_destination + needs_subquery? = + used_aggregates != [] || Map.get(relationship, :from_many?) - _ -> - subquery(relationship_destination) + relationship_destination = + if needs_subquery? do + subquery(relationship_destination) + else + relationship_destination end query = @@ -807,7 +855,8 @@ defmodule AshPostgres.Join do path, kind, source, - filter + filter, + sort? ) do full_path = path ++ [relationship.name] initial_ash_bindings = query.__ash_bindings__ @@ -859,6 +908,7 @@ defmodule AshPostgres.Join do relationship.destination, relationship, query, + sort?, full_path, root_bindings ) do @@ -871,6 +921,15 @@ defmodule AshPostgres.Join do |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) + needs_subquery? = Map.get(relationship, :from_many?) + + relationship_destination = + if needs_subquery? do + subquery(relationship_destination) + else + relationship_destination + end + binding_kinds = case kind do :left -> diff --git a/lib/sort.ex b/lib/sort.ex index 92112ff1..2b8af7f3 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -8,7 +8,7 @@ defmodule AshPostgres.Sort do resource, relationship_path \\ [], binding \\ 0, - return_order_by? \\ false + type \\ :window ) do query = AshPostgres.DataLayer.default_bindings(query, resource) @@ -187,25 +187,34 @@ defmodule AshPostgres.Sort do end) |> case do {:ok, []} -> - {:ok, query} + if type == :return do + {:ok, [], query} + else + {:ok, query} + end {:ok, sort_exprs} -> - if return_order_by? do - {:ok, order_to_fragments(sort_exprs)} - else - new_query = Ecto.Query.order_by(query, ^sort_exprs) + case type do + :return -> + {:ok, order_to_fragments(sort_exprs), query} + + :window -> + new_query = Ecto.Query.order_by(query, ^sort_exprs) - sort_expr = List.last(new_query.order_bys) + sort_expr = List.last(new_query.order_bys) - new_query = - new_query - |> Map.update!(:windows, fn windows -> - order_by_expr = %{sort_expr | expr: [order_by: sort_expr.expr]} - Keyword.put(windows, :order, order_by_expr) - end) - |> Map.update!(:__ash_bindings__, &Map.put(&1, :__order__?, true)) + new_query = + new_query + |> Map.update!(:windows, fn windows -> + order_by_expr = %{sort_expr | expr: [order_by: sort_expr.expr]} + Keyword.put(windows, :order, order_by_expr) + end) + |> Map.update!(:__ash_bindings__, &Map.put(&1, :__order__?, true)) - {:ok, new_query} + {:ok, new_query} + + :direct -> + {:ok, query |> Ecto.Query.order_by(^sort_exprs) |> set_sort_applied()} end {:error, error} -> @@ -214,6 +223,10 @@ defmodule AshPostgres.Sort do end end + defp set_sort_applied(query) do + Map.update!(query, :__ash_bindings__, &Map.put(&1, :sort_applied?, true)) + end + def find_aggregate_binding(bindings, relationship_path, sort) do Enum.find_value( bindings, From ae90b660563b0f97f56dc01107c42b29989b2416 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 17:55:27 -0500 Subject: [PATCH 0091/1215] chore: credo/dialyzer --- .credo.exs | 2 +- lib/join.ex | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.credo.exs b/.credo.exs index a986fd09..fd0b1164 100644 --- a/.credo.exs +++ b/.credo.exs @@ -117,7 +117,7 @@ # {Credo.Check.Refactor.CondStatements, []}, {Credo.Check.Refactor.CyclomaticComplexity, false}, - {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.FunctionArity, [max_arity: 9]}, {Credo.Check.Refactor.LongQuoteBlocks, []}, {Credo.Check.Refactor.MapInto, []}, {Credo.Check.Refactor.MatchInCondition, []}, diff --git a/lib/join.ex b/lib/join.ex index 90d809d2..543d4ff9 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -289,14 +289,7 @@ defmodule AshPostgres.Join do from(row in query, as: ^0) end - query = - case query do - %{__ash_bindings__: ash_bindings} -> - ash_bindings - - _ -> - AshPostgres.DataLayer.default_bindings(query, destination) - end + query = AshPostgres.DataLayer.default_bindings(query, destination) {:ok, order_by, query} = AshPostgres.Sort.sort(query, sort, query.__ash_bindings__.resource, [], 0, :return) From abbbddf726e6390baa4bd31d802812d9541d6de0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 Nov 2023 18:08:12 -0500 Subject: [PATCH 0092/1215] fix: don't add filter for `no_attributes?` relationships --- ....md => DSL:-AshPostgres.DataLayer.cheatmd} | 144 +++++++++--------- lib/aggregate.ex | 12 +- 2 files changed, 79 insertions(+), 77 deletions(-) rename documentation/dsls/{DSL:-AshPostgres.DataLayer.md => DSL:-AshPostgres.DataLayer.cheatmd} (97%) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd similarity index 97% rename from documentation/dsls/DSL:-AshPostgres.DataLayer.md rename to documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index 64982389..f06f7527 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -36,7 +36,7 @@ end ### Options - +
@@ -57,13 +57,13 @@ end @@ -74,7 +74,7 @@ end migrate? - +
Name - atom + module | (any, any -> any) - + - The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more + The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more. Can also be a function that takes a resource and a type `:read | :mutate` and returns the repo
boolean @@ -94,7 +94,7 @@ end migration_types - + Keyword.t @@ -114,7 +114,7 @@ end migration_defaults - + Keyword.t @@ -135,13 +135,13 @@ end base_filter_sql - + String.t - + A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter @@ -155,7 +155,7 @@ end simple_join_first_aggregates - + list(atom) @@ -176,10 +176,10 @@ end skip_unique_indexes - + - list(atom) | atom + atom | list(atom) false @@ -196,7 +196,7 @@ end unique_index_names - + list({list(atom), String.t} | {list(atom), String.t, String.t}) @@ -217,7 +217,7 @@ end exclusion_constraint_names - + `any` @@ -238,7 +238,7 @@ end identity_index_names - + `any` @@ -259,7 +259,7 @@ end foreign_key_names - + list({atom | String.t, String.t} | {atom | String.t, String.t, String.t}) @@ -280,7 +280,7 @@ end migration_ignore_attributes - + list(atom) @@ -301,13 +301,13 @@ end table - + String.t - + The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. @@ -322,13 +322,13 @@ end schema - + String.t - + The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. @@ -343,7 +343,7 @@ end polymorphic? - + boolean @@ -420,13 +420,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" fields - + - list(atom | String.t) | atom | String.t + atom | String.t | list(atom | String.t) - + The fields to include in the index. @@ -454,13 +454,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" name - + String.t - + the name of the index. Defaults to "#{table}_#{column}_index". @@ -474,7 +474,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" unique - + boolean @@ -494,7 +494,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" concurrently - + boolean @@ -514,13 +514,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" using - + String.t - + configures the index type. @@ -534,13 +534,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" prefix - + String.t - + specify an optional prefix for the index. @@ -554,13 +554,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" where - + String.t - + specify conditions for a partial index. @@ -574,13 +574,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" message - + String.t - + A custom message to use for unique indexes that have been violated @@ -594,13 +594,13 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" include - + list(String.t) - + specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. @@ -700,7 +700,7 @@ end atom - + The name of the statement, must be unique within the resource @@ -736,7 +736,7 @@ end String.t - + How to create the structure of the statement @@ -758,7 +758,7 @@ end String.t - + How to tear down the structure of the statement @@ -772,7 +772,7 @@ end code? - + boolean @@ -840,10 +840,10 @@ end - list(String.t | atom) | String.t | atom + String.t | atom | list(String.t | atom) - + A template that will cause the resource to create/manage the specified schema. @@ -858,7 +858,7 @@ end create? - + boolean @@ -878,7 +878,7 @@ end update? - + boolean @@ -938,13 +938,13 @@ end polymorphic_on_delete - + :delete | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. @@ -958,13 +958,13 @@ end polymorphic_on_update - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -978,13 +978,13 @@ end polymorphic_name - + :update | :nilify | :nothing | :restrict - + For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. @@ -1046,7 +1046,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post atom - + The relationship to be configured @@ -1074,13 +1074,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post ignore? - + boolean - + If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way @@ -1094,13 +1094,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_delete - + :delete | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced record of the *destination* resource is deleted. @@ -1115,13 +1115,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post on_update - + :update | :nilify | :nothing | :restrict - + What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. @@ -1136,7 +1136,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post deferrable - + false | true | :initially @@ -1157,13 +1157,13 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post name - + String.t - + The name of the foreign key to generate in the database. Defaults to __fkey @@ -1254,7 +1254,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: `any` + + + + + + + + + + + + + +
- + The attribute or list of attributes to which an error will be added if the check constraint fails @@ -1275,7 +1275,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: String.t - + The name of the constraint @@ -1303,13 +1303,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: message - + String.t - + The message to be added if the check constraint fails @@ -1323,13 +1323,13 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: check - + String.t - + The contents of the check. If this is set, the migration generator will include it when generating migrations @@ -1346,3 +1346,9 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: ### Introspection Target: `AshPostgres.CheckConstraint` + + + + + + diff --git a/lib/aggregate.ex b/lib/aggregate.ex index eb843cb0..757be4c5 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -514,20 +514,16 @@ defmodule AshPostgres.Aggregate do ) do field = first_relationship.destination_attribute - subquery = - from(row in subquery, - where: - field(parent_as(^source_binding), ^first_relationship.source_attribute) == - field(as(^0), ^first_relationship.destination_attribute) - ) - subquery = if Map.get(first_relationship, :no_attributes?) do subquery else from(row in subquery, group_by: field(row, ^field), - select_merge: map(row, ^[field]) + select_merge: map(row, ^[field]), + where: + field(parent_as(^source_binding), ^first_relationship.source_attribute) == + field(as(^0), ^first_relationship.destination_attribute) ) end From 06510b277023154abc76e6be6121398f7abd8e1d Mon Sep 17 00:00:00 2001 From: Daniel Newman Date: Fri, 17 Nov 2023 09:12:00 +1000 Subject: [PATCH 0093/1215] Error using calc to agg to calc to agg --- test/calculation_test.exs | 63 +++++++++++++++++++++++++++++++- test/support/resources/author.ex | 12 ++++++ test/support/resources/post.ex | 4 ++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 9cfe8b61..d0e4f6ed 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.CalculationTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Account, Api, Author, Comment, Post, User} + alias AshPostgres.Test.{Account, Api, Author, Comment, Organization, Post, Profile, User} require Ash.Query import Ash.Expr @@ -612,4 +612,65 @@ defmodule AshPostgres.CalculationTest do |> Map.get(:posts) |> Enum.map(&Map.get(&1, :calc_returning_json)) end + + @tag :focus + test "calculation passes actor to aggregate from calculation on aggregate" do + org = + Organization + |> Ash.Changeset.new(%{name: "The Org"}) + |> Api.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{is_active: true}) + |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + |> Api.create!() + + profile = + Profile + |> Ash.Changeset.for_create(:create, %{description: "Prolific describer of worlds..."}) + |> Api.create!() + + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Foo", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Ash.Changeset.manage_relationship(:profile, profile, type: :append) + |> Api.create!() + + created_post = + Post + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + can_get_author_description_post = + Post + |> Ash.Query.filter(id == ^created_post.id) + |> Ash.Query.load(author: :description) + |> Api.read_one!(actor: user) + + assert can_get_author_description_post.author.description == "actor" + + can_get_author_description_from_aggregate_post = + Post + |> Ash.Query.filter(id == ^created_post.id) + |> Ash.Query.load(:author_profile_description) + |> Api.read_one!(actor: user) + + assert can_get_author_description_from_aggregate_post.author_profile_description == + "Prolific describer of worlds..." + + can_get_author_description_from_calculation_of_aggregate_post = + Post + |> Ash.Query.filter(id == ^created_post.id) + |> Ash.Query.load(:author_profile_description_from_agg) + |> Api.read_one!(actor: user) + + assert can_get_author_description_from_calculation_of_aggregate_post.author_profile_description_from_agg == + "Prolific describer of worlds..." + end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 46c6abb1..92d005f8 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -30,6 +30,18 @@ defmodule AshPostgres.Test.Author do end calculations do + calculate( + :description, + :string, + expr( + if is_nil(^actor(:id)) do + "no actor" + else + "actor found" + end + ) + ) + calculate(:title, :string, expr(bio[:title])) calculate(:full_name, :string, expr(first_name <> " " <> last_name)) calculate(:full_name_with_nils, :string, expr(string_join([first_name, last_name], " "))) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 4f3cc66f..6195a000 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -244,6 +244,8 @@ defmodule AshPostgres.Test.Post do ) calculate(:author_first_name_calc, :string, expr(author.first_name)) + + calculate(:author_profile_description_from_agg, :string, expr(author_profile_description)) end aggregates do @@ -360,6 +362,8 @@ defmodule AshPostgres.Test.Post do first :latest_arbitrary_timestamp, :comments, :arbitrary_timestamp do sort(arbitrary_timestamp: :desc) end + + first(:author_profile_description, :author, :description) end end From c6e6633a9a83be65de0ca7c7b2cf1bedf8844512 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 04:01:35 -0500 Subject: [PATCH 0094/1215] fix: properly expand calculation values across aggregate invocations --- lib/aggregate.ex | 49 ++++++++++++++++++++++++++++++++------- lib/calculation.ex | 9 +++++++ lib/expr.ex | 40 +++++++++++++++++++++++++++----- test/calculation_test.exs | 7 +++--- 4 files changed, 86 insertions(+), 19 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 757be4c5..d090a566 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -287,15 +287,45 @@ defmodule AshPostgres.Aggregate do false ) do {:ok, query} -> - binding = - AshPostgres.DataLayer.get_binding( - resource, - aggregate.relationship_path, - query, - [:left, :inner] - ) + ref = + %Ash.Query.Ref{ + attribute: + aggregate_field( + aggregate, + Ash.Resource.Info.related( + resource, + path ++ aggregate.relationship_path + ), + path ++ aggregate.relationship_path, + query + ), + relationship_path: path ++ aggregate.relationship_path, + resource: resource + } + + value = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + + type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) + + with_default = + if aggregate.default_value do + if type do + Ecto.Query.dynamic(coalesce(^value, type(^aggregate.default_value, ^type))) + else + Ecto.Query.dynamic(coalesce(^value, ^aggregate.default_value)) + end + else + value + end - {:ok, query, Ecto.Query.dynamic(field(as(^binding), ^aggregate.field))} + casted = + if type do + Ecto.Query.dynamic(type(^with_default, ^type)) + else + with_default + end + + {:ok, query, casted} {:error, error} -> {:error, error} @@ -1112,7 +1142,8 @@ defmodule AshPostgres.Aggregate do relationship.type == :belongs_to && single_path?(relationship.destination, rest) end - defp aggregate_field(aggregate, resource, _relationship_path, query) do + @doc false + def aggregate_field(aggregate, resource, _relationship_path, query) do case Ash.Resource.Info.field( resource, aggregate.field || List.first(Ash.Resource.Info.primary_key(resource)) diff --git a/lib/calculation.ex b/lib/calculation.ex index 81c68aaf..fe5744c1 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -60,6 +60,15 @@ defmodule AshPostgres.Calculation do Map.get(calculation, :constraints, []) ) + expression = + Ash.Actions.Read.add_calc_context_to_filter( + expression, + calculation.context[:actor], + calculation.context[:authorize?], + calculation.context[:tenant], + calculation.context[:tracer] + ) + expr = AshPostgres.Expr.dynamic_expr( query, diff --git a/lib/expr.ex b/lib/expr.ex index 9a8758ff..f3fd5204 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -874,7 +874,7 @@ defmodule AshPostgres.Expr do first_optimized_aggregate? = AshPostgres.Aggregate.optimizable_first_aggregate?(related, aggregate) - {ref_binding, field_name} = + {ref_binding, field_name, value} = if first_optimized_aggregate? do ref = %{ref | relationship_path: ref.relationship_path ++ aggregate.relationship_path} ref_binding = ref_binding(ref, bindings) @@ -883,7 +883,31 @@ defmodule AshPostgres.Expr do raise "Error while building reference: #{inspect(ref)}" end - {ref_binding, aggregate.field} + ref = + %Ash.Query.Ref{ + attribute: + AshPostgres.Aggregate.aggregate_field( + aggregate, + Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path), + aggregate.relationship_path, + query + ), + relationship_path: ref.relationship_path, + resource: query.__ash_bindings__.resource + } + + ref = + Ash.Actions.Read.add_calc_context_to_filter( + ref, + aggregate.context[:actor], + aggregate.context[:authorize?], + aggregate.context[:tenant], + aggregate.context[:tracer] + ) + + value = dynamic_expr(query, ref, query.__ash_bindings__, false) + + {ref_binding, aggregate.field, value} else ref_binding = ref_binding(ref, bindings) @@ -891,7 +915,7 @@ defmodule AshPostgres.Expr do raise "Error while building reference: #{inspect(ref)}" end - {ref_binding, aggregate.name} + {ref_binding, aggregate.name, nil} end ref_binding = @@ -902,10 +926,14 @@ defmodule AshPostgres.Expr do end expr = - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(field(parent_as(^ref_binding), ^field_name)) + if value do + value else - Ecto.Query.dynamic(field(as(^ref_binding), ^field_name)) + if query.__ash_bindings__[:parent?] do + Ecto.Query.dynamic(field(parent_as(^ref_binding), ^field_name)) + else + Ecto.Query.dynamic(field(as(^ref_binding), ^field_name)) + end end type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index d0e4f6ed..cdfd83b7 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -613,7 +613,6 @@ defmodule AshPostgres.CalculationTest do |> Enum.map(&Map.get(&1, :calc_returning_json)) end - @tag :focus test "calculation passes actor to aggregate from calculation on aggregate" do org = Organization @@ -653,7 +652,7 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.load(author: :description) |> Api.read_one!(actor: user) - assert can_get_author_description_post.author.description == "actor" + assert can_get_author_description_post.author.description == "actor found" can_get_author_description_from_aggregate_post = Post @@ -662,7 +661,7 @@ defmodule AshPostgres.CalculationTest do |> Api.read_one!(actor: user) assert can_get_author_description_from_aggregate_post.author_profile_description == - "Prolific describer of worlds..." + "actor found" can_get_author_description_from_calculation_of_aggregate_post = Post @@ -671,6 +670,6 @@ defmodule AshPostgres.CalculationTest do |> Api.read_one!(actor: user) assert can_get_author_description_from_calculation_of_aggregate_post.author_profile_description_from_agg == - "Prolific describer of worlds..." + "actor found" end end From 4fa16b7524a342922e846ee22bc21fba462999c7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 06:38:48 -0500 Subject: [PATCH 0095/1215] test: add pg versions to testing matrix --- .github/workflows/elixir.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 3e0570b8..1eeac858 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -8,6 +8,9 @@ on: branches: [main] jobs: ash-ci: + strategy: + matrix: + postgres_version: ["10", "13", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true From b52fc0b429f2d5a1e486001a72875a6c38745145 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 07:10:37 -0500 Subject: [PATCH 0096/1215] chore: improve matrix behavior and update exists tests --- .github/workflows/elixir.yml | 3 ++- test/aggregate_test.exs | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 1eeac858..e3c4c854 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -9,8 +9,9 @@ on: jobs: ash-ci: strategy: + fail-fast: false matrix: - postgres_version: ["10", "13", "16"] + postgres_version: ["10", "11", "12", "13", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 662d23c6..a7c1bdb9 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -209,12 +209,17 @@ defmodule AshPostgres.AggregateTest do end describe "exists" do - test "with data and a filter, it returns the count" do + test "with data and a filter, it returns the correct result" do post = Post |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() + Comment + |> Ash.Changeset.new(%{title: "non-match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + assert %{has_comment_called_match: false} = Post |> Ash.Query.filter(id == ^post.id) From cef3d20eaf2f31a274e2fb136833cfc227959e2c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 07:11:25 -0500 Subject: [PATCH 0097/1215] chore: add tons of versions in github CI --- .github/workflows/elixir.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index e3c4c854..1c93026b 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - postgres_version: ["10", "11", "12", "13", "16"] + postgres_version: ["10", "11", "12", "13", "14", "15", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true From 33bd0a7061c91f9e36d54158164dff064a542dde Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 07:46:43 -0500 Subject: [PATCH 0098/1215] fix: properly add filters for exists aggregates closes: #173 --- lib/aggregate.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index d090a566..2d86592c 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -94,10 +94,17 @@ defmodule AshPostgres.Aggregate do is_single? && Enum.at(aggregates, 0).kind == :exists -> [aggregate] = aggregates + expr = + if is_nil(Map.get(aggregate.query, :filter)) do + true + else + Map.get(aggregate.query, :filter) + end + exists = AshPostgres.Expr.dynamic_expr( query, - %Ash.Query.Exists{path: aggregate.relationship_path, expr: true}, + %Ash.Query.Exists{path: aggregate.relationship_path, expr: expr}, query.__ash_bindings__ ) From bdcbd647f3bdd0664ec3cb6171e3dc277f64d355 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 09:03:25 -0500 Subject: [PATCH 0099/1215] fix: apply limit to `from_many?` relationship joins --- lib/join.ex | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 543d4ff9..15640d62 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -102,7 +102,8 @@ defmodule AshPostgres.Join do opts, [{join_type, rest_rels}], current_path, - source + source, + sort? ) do {:ok, query} -> {:cont, {:ok, query}} @@ -130,7 +131,8 @@ defmodule AshPostgres.Join do opts, [{join_type, rest_rels}], current_path, - source + source, + sort? ) do {:ok, query} -> {:cont, {:ok, query}} @@ -637,7 +639,7 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(relationship_destination) + subquery(from row in relationship_destination, limit: 1) else relationship_destination end @@ -795,7 +797,7 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(relationship_destination) + subquery(from row in relationship_destination, limit: 1) else relationship_destination end @@ -918,7 +920,7 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(relationship_destination) + subquery(from row in relationship_destination, limit: 1) else relationship_destination end From 944a4e128eef25fe3531964bceb23e916d66fbed Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 17 Nov 2023 09:17:11 -0500 Subject: [PATCH 0100/1215] chore: format and fix matrix --- .github/workflows/elixir.yml | 3 +-- lib/join.ex | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 1c93026b..e079cc88 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -9,9 +9,8 @@ on: jobs: ash-ci: strategy: - fail-fast: false matrix: - postgres_version: ["10", "11", "12", "13", "14", "15", "16"] + postgres_version: ["10", "12", "14", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true diff --git a/lib/join.ex b/lib/join.ex index 15640d62..561bb3db 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -639,7 +639,7 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(from row in relationship_destination, limit: 1) + subquery(from(row in relationship_destination, limit: 1)) else relationship_destination end @@ -797,7 +797,7 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(from row in relationship_destination, limit: 1) + subquery(from(row in relationship_destination, limit: 1)) else relationship_destination end @@ -920,7 +920,7 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(from row in relationship_destination, limit: 1) + subquery(from(row in relationship_destination, limit: 1)) else relationship_destination end From 48e8a414da86c1f8d3847180bc7771c849d58fb8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 20 Nov 2023 06:38:43 -0500 Subject: [PATCH 0101/1215] chore: add error context to hydrate references --- lib/expr.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index f3fd5204..7559bffd 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -811,7 +811,7 @@ defmodule AshPostgres.Expr do {:error, error} -> raise """ - Failed to hydrate references in #{inspect(calculation.module.expression(calculation.opts, calculation.context))} + Failed to hydrate references for resource #{inspect(resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))} #{inspect(error)} """ @@ -1039,7 +1039,7 @@ defmodule AshPostgres.Expr do end _ -> - raise "Failed to hydrate references in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}" + raise "Failed to hydrate references for #{inspect(ref.resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}" end end From 4278f3173689db1b160129a15bc93a300e19b3e8 Mon Sep 17 00:00:00 2001 From: Daniel Newman Date: Mon, 20 Nov 2023 14:48:38 +1000 Subject: [PATCH 0102/1215] Implement test and resources to show reference bug --- test/complex_calculations_test.exs | 73 +++++++++++++++++++ test/support/complex_calculations/registry.ex | 1 + .../complex_calculations/resources/channel.ex | 14 ++++ .../resources/channel_member.ex | 5 +- .../resources/dm_channel.ex | 72 ++++++++++++++++++ 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 test/support/complex_calculations/resources/dm_channel.ex diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 01f35e1e..cb749d43 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -1,4 +1,5 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do + alias AshPostgres.Test.ComplexCalculations.Channel use AshPostgres.RepoCase, async: false test "complex calculation" do @@ -112,4 +113,76 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do assert channel.name == user_2.name end + + test "complex calculation while using actor on related resource passes reference" do + dm_channel = + AshPostgres.Test.ComplexCalculations.DMChannel + |> Ash.Changeset.new() + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + user_1 = + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "User 1"}) + |> AshPostgres.Test.Api.create!() + + user_2 = + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "User 2"}) + |> AshPostgres.Test.Api.create!() + + channel_member_1 = + AshPostgres.Test.ComplexCalculations.ChannelMember + |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_1.id}) + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + channel_member_2 = + AshPostgres.Test.ComplexCalculations.ChannelMember + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:channel, dm_as_channel, type: :append) + |> Ash.Changeset.manage_relationship(:user, user_2, type: :append) + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + dm_channel = + dm_channel + |> AshPostgres.Test.ComplexCalculations.Api.load!([ + :first_member, + :second_member + ]) + + assert dm_channel.first_member.id == channel_member_1.id + assert dm_channel.second_member.id == channel_member_2.id + + dm_channel = + dm_channel + |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_1) + + assert dm_channel.name == user_1.name + + dm_channel = + dm_channel + |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_2) + + assert dm_channel.name == user_2.name + + channels = + AshPostgres.Test.ComplexCalculations.Channel + |> Ash.Query.for_read(:read) + |> AshPostgres.Test.ComplexCalculations.Api.read!() + + channels = + channels + |> AshPostgres.Test.ComplexCalculations.Api.load!([dm_channel: :name], + actor: user_1 + ) + + [channel | _] = channels + + assert channel.dm_channel.name == user_1.name + + channel = + channel + |> AshPostgres.Test.ComplexCalculations.Api.load(:dm_name, actor: user_1) + + assert channel.dm_name == user_1.name + end end diff --git a/test/support/complex_calculations/registry.ex b/test/support/complex_calculations/registry.ex index 13232670..29f3afcb 100644 --- a/test/support/complex_calculations/registry.ex +++ b/test/support/complex_calculations/registry.ex @@ -7,6 +7,7 @@ defmodule AshPostgres.Test.ComplexCalculations.Registry do entry(AshPostgres.Test.ComplexCalculations.Skill) entry(AshPostgres.Test.ComplexCalculations.Documentation) entry(AshPostgres.Test.ComplexCalculations.Channel) + entry(AshPostgres.Test.ComplexCalculations.DMChannel) entry(AshPostgres.Test.ComplexCalculations.ChannelMember) end end diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex index b357983c..188898e7 100644 --- a/test/support/complex_calculations/resources/channel.ex +++ b/test/support/complex_calculations/resources/channel.ex @@ -36,11 +36,17 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do from_many?(true) sort(created_at: :desc) end + + has_one :dm_channel, AshPostgres.Test.ComplexCalculations.DMChannel do + api(AshPostgres.Test.ComplexCalculations.Api) + destination_attribute(:id) + end end aggregates do first(:first_member_name, [:first_member, :user], :name) first(:second_member_name, [:second_member, :user], :name) + first(:dm_channel_name, [:dm_channel], :name) end calculations do @@ -60,5 +66,13 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do ) ) end + + calculate(:dm_name, :string, expr(dm_channel_name)) + end + + policies do + policy always() do + authorize_if(always()) + end end end diff --git a/test/support/complex_calculations/resources/channel_member.ex b/test/support/complex_calculations/resources/channel_member.ex index dd5a8a8b..0d78a294 100644 --- a/test/support/complex_calculations/resources/channel_member.ex +++ b/test/support/complex_calculations/resources/channel_member.ex @@ -21,7 +21,8 @@ defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do end relationships do - belongs_to(:user, AshPostgres.Test.User, api: AshPostgres.Test.Api) - belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel) + belongs_to(:user, AshPostgres.Test.User, api: AshPostgres.Test.Api, attribute_writable?: true) + + belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel, attribute_writable?: true) end end diff --git a/test/support/complex_calculations/resources/dm_channel.ex b/test/support/complex_calculations/resources/dm_channel.ex new file mode 100644 index 00000000..a5d71004 --- /dev/null +++ b/test/support/complex_calculations/resources/dm_channel.ex @@ -0,0 +1,72 @@ +defmodule AshPostgres.Test.ComplexCalculations.DMChannel do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + require Ash.Expr + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + create_timestamp(:created_at, private?: false) + update_timestamp(:updated_at, private?: false) + end + + postgres do + table "complex_calculations_channels" + repo(AshPostgres.TestRepo) + end + + relationships do + has_many :channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember do + destination_attribute(:channel_id) + end + + has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + destination_attribute(:channel_id) + from_many?(true) + sort(created_at: :asc) + end + + has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + destination_attribute(:channel_id) + from_many?(true) + sort(created_at: :desc) + end + end + + aggregates do + first(:first_member_name, [:first_member, :user], :name) + first(:second_member_name, [:second_member, :user], :name) + end + + calculations do + calculate :name, :string do + calculation( + expr( + cond do + first_member.user_id == ^actor(:id) -> + first_member_name + + second_member.user_id == ^actor(:id) -> + second_member_name + + true -> + first_member_name <> ", " <> second_member_name + end + ) + ) + end + end + + policies do + policy always() do + authorize_if(always()) + end + end +end From fcda62705023e0ba786cb11ac4bfda4f46bb0833 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 20 Nov 2023 07:05:47 -0500 Subject: [PATCH 0103/1215] fix: hydrate aggregate refs when adding for calculations --- lib/aggregate.ex | 32 ++++++++++++++++++++++++++++++ lib/calculation.ex | 3 ++- test/complex_calculations_test.exs | 9 +++------ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 2d86592c..b6a8e256 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -396,6 +396,34 @@ defmodule AshPostgres.Aggregate do used_calculations end + exprs = + Enum.map(used_calculations, fn calculation -> + case Ash.Filter.hydrate_refs( + calculation.module.expression(calculation.opts, calculation.context), + %{ + resource: aggregate.query.resource, + aggregates: %{}, + calculations: %{}, + public?: false + } + ) do + {:ok, hydrated} -> + hydrated + + {:error, _error} -> + raise """ + Failed to hydrate references for resource #{inspect(aggregate.query.resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))} + """ + end + end) + + {:ok, agg_query} = + AshPostgres.Join.join_all_relationships( + agg_query, + exprs, + [] + ) + used_aggregates = used_aggregates( filter, @@ -1164,6 +1192,10 @@ defmodule AshPostgres.Aggregate do AshPostgres.Expr.validate_type!(query, calc_type, "#{inspect(calculation.name)}") + if aggregate.context == %{} do + raise "what" + end + {:ok, query_calc} = Ash.Query.Calculation.new( calculation.name, diff --git a/lib/calculation.ex b/lib/calculation.ex index fe5744c1..d3d44a2a 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -20,7 +20,7 @@ defmodule AshPostgres.Calculation do aggregates = calculations - |> Enum.flat_map(fn {_calculation, expression} -> + |> Enum.flat_map(fn {calculation, expression} -> used_calculations = Ash.Filter.used_calculations( expression, @@ -34,6 +34,7 @@ defmodule AshPostgres.Calculation do used_calculations, [] ) + |> Enum.map(&Map.put(&1, :context, calculation.context)) end) |> Enum.uniq() diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index cb749d43..ac1c75cb 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -1,5 +1,4 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do - alias AshPostgres.Test.ComplexCalculations.Channel use AshPostgres.RepoCase, async: false test "complex calculation" do @@ -137,9 +136,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do channel_member_2 = AshPostgres.Test.ComplexCalculations.ChannelMember - |> Ash.Changeset.new() - |> Ash.Changeset.manage_relationship(:channel, dm_as_channel, type: :append) - |> Ash.Changeset.manage_relationship(:user, user_2, type: :append) + |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_2.id}) |> AshPostgres.Test.ComplexCalculations.Api.create!() dm_channel = @@ -181,8 +178,8 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do channel = channel - |> AshPostgres.Test.ComplexCalculations.Api.load(:dm_name, actor: user_1) + |> AshPostgres.Test.ComplexCalculations.Api.load!(:dm_name, actor: user_2) - assert channel.dm_name == user_1.name + assert channel.dm_name == user_2.name end end From 021b7e42338efbc31c3baee299a14fb0efe0f810 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Mon, 20 Nov 2023 22:52:50 +0100 Subject: [PATCH 0104/1215] improvement: allow specifying multi-column foreign keys (#180) * improvement: add match_with option on references * improvement: add match_type option on references --- .formatter.exs | 2 + .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 40 ++++++ lib/data_layer.ex | 3 +- .../migration_generator.ex | 16 +++ lib/migration_generator/operation.ex | 82 +++++++++++-- lib/reference.ex | 20 ++- ...te_multitenancy_and_non_full_match_type.ex | 52 ++++++++ test/migration_generator_test.exs | 114 ++++++++++++++++++ test/references_test.exs | 103 ++++++++++++++++ 9 files changed, 420 insertions(+), 12 deletions(-) create mode 100644 lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex create mode 100644 test/references_test.exs diff --git a/.formatter.exs b/.formatter.exs index c0f0d5d3..95e0deb8 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -15,6 +15,8 @@ spark_locals_without_parens = [ include: 1, index: 1, index: 2, + match_type: 1, + match_with: 1, message: 1, migrate?: 1, migration_defaults: 1, diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd index f06f7527..6e007731 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd @@ -1170,6 +1170,46 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post
+ + + match_with + + + + + Keyword.t + + + + Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute. +
+ + + match_type + + + + + :simple | :partial | :full + + + + select if the match is `:simple`, `:partial`, or `:full` +
diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6037e4d9..9042754a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -392,7 +392,8 @@ defmodule AshPostgres.DataLayer do transformers: [ AshPostgres.Transformers.ValidateReferences, AshPostgres.Transformers.EnsureTableOrPolymorphic, - AshPostgres.Transformers.PreventMultidimensionalArrayAggregates + AshPostgres.Transformers.PreventMultidimensionalArrayAggregates, + AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchType ] def migrate(args) do diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index c9dc2d98..721f9ea7 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -707,6 +707,8 @@ defmodule AshPostgres.MigrationGenerator do primary_key?: merge_uniq!(references, table, :primary_key?, name), on_delete: merge_uniq!(references, table, :on_delete, name), on_update: merge_uniq!(references, table, :on_update, name), + match_with: merge_uniq!(references, table, :match_with, name) |> to_map(), + match_type: merge_uniq!(references, table, :match_type, name), name: merge_uniq!(references, table, :name, name), table: merge_uniq!(references, table, :table, name), schema: merge_uniq!(references, table, :schema, name) @@ -714,6 +716,9 @@ defmodule AshPostgres.MigrationGenerator do end end + defp to_map(nil), do: nil + defp to_map(kw_list) when is_list(kw_list), do: Map.new(kw_list) + defp merge_uniq!(references, table, field, attribute) do references |> Enum.map(&Map.get(&1, field)) @@ -2675,6 +2680,8 @@ defmodule AshPostgres.MigrationGenerator do multitenancy: multitenancy(relationship.destination), on_delete: configured_reference.on_delete, on_update: configured_reference.on_update, + match_with: configured_reference.match_with, + match_type: configured_reference.match_type, name: configured_reference.name, primary_key?: destination_attribute.primary_key?, schema: @@ -2700,6 +2707,8 @@ defmodule AshPostgres.MigrationGenerator do |> Kernel.||(%{ on_delete: nil, on_update: nil, + match_with: nil, + match_type: nil, deferrable: false, schema: relationship.context[:data_layer][:schema] || @@ -3029,6 +3038,13 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put_new(:on_update, nil) |> Map.update!(:on_delete, &(&1 && String.to_atom(&1))) |> Map.update!(:on_update, &(&1 && String.to_atom(&1))) + |> Map.put_new(:match_with, nil) + |> Map.put_new(:match_type, nil) + |> Map.update!( + :match_with, + &(&1 && Enum.into(&1, %{}, fn {k, v} -> {String.to_atom(k), String.to_atom(v)} end)) + ) + |> Map.update!(:match_type, &(&1 && String.to_atom(&1))) |> Map.put( :name, Map.get(references, :name) || "#{table}_#{attribute.source}_fkey" diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index abd9acbf..86f5f113 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -66,6 +66,53 @@ defmodule AshPostgres.MigrationGenerator.Operation do def reference_type(%{type: type}, _) do type end + + def with_match(reference, source_attribute \\ nil) + + def with_match( + %{ + primary_key?: false, + destination_attribute: reference_attribute, + multitenancy: %{strategy: :attribute, attribute: destination_attribute} + } = reference, + source_attribute + ) + when not is_nil(source_attribute) and reference_attribute != destination_attribute do + with_targets = + [{as_atom(source_attribute), as_atom(destination_attribute)}] + |> Enum.into(reference.match_with || %{}) + |> with_targets() + + # We can only have match: :full here, this gets validated by a Transformer + join([with_targets, "match: :full"]) + end + + def with_match(reference, _) do + with_targets = with_targets(reference.match_with) + match_type = match_type(reference.match_type) + + if with_targets != nil or match_type != nil do + join([with_targets, match_type]) + else + nil + end + end + + def with_targets(targets) when is_map(targets) do + targets_string = + targets + |> Enum.map_join(", ", fn {source, destination} -> "#{source}: :#{destination}" end) + + "with: [#{targets_string}]" + end + + def with_targets(_), do: nil + + def match_type(type) when type in [:simple, :partial, :full] do + "match: :#{type}" + end + + def match_type(_), do: nil end defmodule CreateTable do @@ -88,14 +135,11 @@ defmodule AshPostgres.MigrationGenerator.Operation do table: table, destination_attribute: reference_attribute, schema: destination_schema, - multitenancy: %{strategy: :attribute, attribute: destination_attribute} + multitenancy: %{strategy: :attribute} } = reference } = attribute }) do - with_match = - if !reference.primary_key? && destination_attribute != reference_attribute do - "with: [#{as_atom(source_attribute)}: :#{as_atom(destination_attribute)}], match: :full" - end + with_match = with_match(reference, source_attribute) size = if attribute[:size] do @@ -136,6 +180,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = reference } = attribute }) do + with_match = with_match(reference) + size = if attribute[:size] do "size: #{attribute[:size]}" @@ -146,6 +192,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do "references(:#{as_atom(table)}", [ "column: #{inspect(destination_attribute)}", + with_match, option("prefix", destination_schema), "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", @@ -198,6 +245,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = reference } = attribute }) do + with_match = with_match(reference) + size = if attribute[:size] do "size: #{attribute[:size]}" @@ -208,6 +257,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do "references(:#{as_atom(table)}", [ "column: #{inspect(destination_attribute)}", + with_match, "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", "prefix: prefix()", @@ -236,6 +286,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = reference } = attribute }) do + with_match = with_match(reference) + size = if attribute[:size] do "size: #{attribute[:size]}" @@ -251,6 +303,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do "references(:#{as_atom(table)}", [ "column: #{inspect(destination_attribute)}", + with_match, "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", option("prefix", destination_schema), @@ -277,6 +330,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = reference } = attribute }) do + with_match = with_match(reference) + size = if attribute[:size] do "size: #{attribute[:size]}" @@ -287,6 +342,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do "references(:#{as_atom(table)}", [ "column: #{inspect(destination_attribute)}", + with_match, "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", option("prefix", destination_schema), @@ -449,6 +505,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = attribute, _schema ) do + with_match = with_match(reference) + size = if attribute[:size] do "size: #{attribute[:size]}" @@ -456,6 +514,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do join([ "references(:#{as_atom(table)}, column: #{inspect(destination_attribute)}", + with_match, "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, @@ -471,7 +530,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do %{ references: %{ - multitenancy: %{strategy: :attribute, attribute: destination_attribute}, + multitenancy: %{strategy: :attribute}, table: table, schema: destination_schema, destination_attribute: reference_attribute @@ -484,10 +543,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do destination_schema end - with_match = - if !reference.primary_key? && destination_attribute != reference_attribute do - "with: [#{as_atom(source_attribute)}: :#{as_atom(destination_attribute)}], match: :full" - end + with_match = with_match(reference, source_attribute) size = if attribute[:size] do @@ -519,6 +575,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = attribute, schema ) do + with_match = with_match(reference) + size = if attribute[:size] do "size: #{attribute[:size]}" @@ -531,6 +589,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do join([ "references(:#{as_atom(table)}, column: #{inspect(destination_attribute)}", + with_match, "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, @@ -553,6 +612,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do } = attribute, schema ) do + with_match = with_match(reference) + destination_schema = if schema != destination_schema do destination_schema @@ -565,6 +626,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do join([ "references(:#{as_atom(table)}, column: #{inspect(destination_attribute)}", + with_match, "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, diff --git a/lib/reference.ex b/lib/reference.ex index c6c164d6..d438cf7e 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -1,6 +1,15 @@ defmodule AshPostgres.Reference do @moduledoc "Represents the configuration of a reference (i.e foreign key)." - defstruct [:relationship, :on_delete, :on_update, :name, :deferrable, ignore?: false] + defstruct [ + :relationship, + :on_delete, + :on_update, + :name, + :match_with, + :match_type, + :deferrable, + ignore?: false + ] def schema do [ @@ -37,6 +46,15 @@ defmodule AshPostgres.Reference do type: :string, doc: "The name of the foreign key to generate in the database. Defaults to __fkey" + ], + match_with: [ + type: :non_empty_keyword_list, + doc: + "Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute." + ], + match_type: [ + type: {:one_of, [:simple, :partial, :full]}, + doc: "select if the match is `:simple`, `:partial`, or `:full`" ] ] end diff --git a/lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex b/lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex new file mode 100644 index 00000000..b3b02e8b --- /dev/null +++ b/lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex @@ -0,0 +1,52 @@ +defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchType do + @moduledoc false + use Spark.Dsl.Transformer + alias Spark.Dsl.Transformer + + def transform(dsl) do + if Transformer.get_option(dsl, [:multitenancy], :strategy) == :attribute do + dsl + |> AshPostgres.DataLayer.Info.references() + |> Enum.filter(&(&1.match_type && &1.match_type != :full)) + |> Enum.each(fn reference -> + relationship = Ash.Resource.Info.relationship(dsl, reference.relationship) + + if uses_attribute_strategy?(relationship) and + not targets_primary_key?(relationship) and + not targets_multitenancy_attribute?(relationship) do + resource = Transformer.get_persisted(dsl, :module) + + raise Spark.Error.DslError, + module: resource, + message: """ + Unsupported match_type. + + The reference #{inspect(resource)}.#{reference.relationship} can't have `match_type: :#{reference.match_type}` because it's referencing another multitenant resource with attribute strategy using a non-primary key index, which requires using `match_type: :full`. + """, + path: [:postgres, :references, reference.relationship] + else + :ok + end + end) + else + {:ok, dsl} + end + end + + defp uses_attribute_strategy?(relationship) do + Ash.Resource.Info.multitenancy_strategy(relationship.destination) == :attribute + end + + defp targets_primary_key?(relationship) do + Ash.Resource.Info.attribute( + relationship.destination, + relationship.destination_attribute + ) + |> Map.fetch!(:primary_key?) + end + + defp targets_multitenancy_attribute?(relationship) do + relationship.destination_attribute == + Ash.Resource.Info.multitenancy_attribute(relationship.destination) + end +end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 785cff05..e9d8fc01 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -828,6 +828,120 @@ defmodule AshPostgres.MigrationGeneratorTest do ~S[references(:posts, column: :id, name: "posts_post_id_fkey", type: :text, prefix: "public")] end + test "references allow passing :match_with and :match_type" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:key_id, :uuid, allow_nil?: false) + attribute(:foobar, :string) + end + end + + defposts Post2 do + attributes do + uuid_primary_key(:id) + attribute(:name, :string) + attribute(:related_key_id, :uuid) + end + + relationships do + belongs_to(:post, Post) + end + + postgres do + references do + reference(:post, match_with: [related_key_id: :key_id], match_type: :partial) + end + end + end + + defapi([Post, Post2]) + + AshPostgres.MigrationGenerator.generate(Api, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ + ~S{references(:posts, column: :id, with: [related_key_id: :key_id], match: :partial, name: "posts_post_id_fkey", type: :uuid, prefix: "public")} + end + + test "references merge :match_with and multitenancy attribute" do + defresource Org, "orgs" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + end + + defresource User, "users" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:secondary_id, :uuid) + attribute(:name, :string) + attribute(:org_id, :uuid) + attribute(:key_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + end + end + + defresource UserThing, "user_things" do + attributes do + attribute(:id, :string, primary_key?: true, allow_nil?: false) + attribute(:name, :string) + attribute(:org_id, :uuid) + attribute(:related_key_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + belongs_to(:user, User, destination_attribute: :secondary_id) + end + + postgres do + references do + reference(:user, match_with: [related_key_id: :key_id], match_type: :full) + end + end + end + + defapi([Org, User, UserThing]) + + AshPostgres.MigrationGenerator.generate(Api, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ + ~S{references(:users, column: :secondary_id, with: [related_key_id: :key_id, org_id: :org_id], match: :full, name: "user_things_user_id_fkey", type: :uuid, prefix: "public")} + end + test "when modified, the foreign key is dropped before modification" do defposts do attributes do diff --git a/test/references_test.exs b/test/references_test.exs new file mode 100644 index 00000000..b192597b --- /dev/null +++ b/test/references_test.exs @@ -0,0 +1,103 @@ +defmodule AshPostgres.ReferencesTest do + use ExUnit.Case + + test "can't use match_type != :full when referencing an non-primary key index" do + Code.compiler_options(ignore_module_conflict: true) + on_exit(fn -> Code.compiler_options(ignore_module_conflict: false) end) + + defmodule Org do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + + postgres do + table("orgs") + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + end + + defmodule User do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:secondary_id, :uuid) + attribute(:foo_id, :uuid) + attribute(:name, :string) + attribute(:org_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + end + + postgres do + table("users") + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + end + + assert_raise Spark.Error.DslError, ~r/Unsupported match_type./, fn -> + defmodule UserThing do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + attribute(:id, :string, primary_key?: true, allow_nil?: false) + attribute(:name, :string) + attribute(:org_id, :uuid) + attribute(:foo_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) + belongs_to(:user, User, destination_attribute: :secondary_id) + end + + postgres do + table("user_things") + repo(AshPostgres.TestRepo) + + references do + reference :user, match_with: [foo_id: :foo_id], match_type: :simple + end + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + end + end + end +end From 8caad3bf7d6e322692bb12dcc954a21e5cf0d5ac Mon Sep 17 00:00:00 2001 From: Daniel Newman Date: Fri, 17 Nov 2023 09:20:54 +1000 Subject: [PATCH 0105/1215] Adjust tests for updates from upstream --- test/calculation_test.exs | 2 +- test/support/resources/author.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index cdfd83b7..7b95b57c 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -652,7 +652,7 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.load(author: :description) |> Api.read_one!(actor: user) - assert can_get_author_description_post.author.description == "actor found" + assert can_get_author_description_post.author.description == "Prolific describer of worlds..." can_get_author_description_from_aggregate_post = Post diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 92d005f8..f42e6bb3 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -37,7 +37,7 @@ defmodule AshPostgres.Test.Author do if is_nil(^actor(:id)) do "no actor" else - "actor found" + profile_description end ) ) From ff6c038757f279e768fb63419e30f8656caf4b7c Mon Sep 17 00:00:00 2001 From: Daniel Newman Date: Fri, 17 Nov 2023 09:12:00 +1000 Subject: [PATCH 0106/1215] Error using calc to agg to calc to agg --- test/calculation_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 7b95b57c..85db12e6 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -661,7 +661,7 @@ defmodule AshPostgres.CalculationTest do |> Api.read_one!(actor: user) assert can_get_author_description_from_aggregate_post.author_profile_description == - "actor found" + "Prolific describer of worlds..." can_get_author_description_from_calculation_of_aggregate_post = Post @@ -670,6 +670,6 @@ defmodule AshPostgres.CalculationTest do |> Api.read_one!(actor: user) assert can_get_author_description_from_calculation_of_aggregate_post.author_profile_description_from_agg == - "actor found" + "Prolific describer of worlds..." end end From 6cc36c9684abd4a9a271609476a0b87b11152728 Mon Sep 17 00:00:00 2001 From: Daniel Newman Date: Mon, 20 Nov 2023 10:46:29 +1000 Subject: [PATCH 0107/1215] Remove test duplicate from merge conflict --- test/calculation_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 85db12e6..9e92eace 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -507,7 +507,7 @@ defmodule AshPostgres.CalculationTest do end describe "maps" do - test "maps can reference filtered aggregats" do + test "maps can reference filtered aggregates" do post = Post |> Ash.Changeset.new(%{title: "match", score: 42}) From 28f28610366ea64bbdc78abe83a8fcbb2ea46073 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 20 Nov 2023 13:47:45 -0500 Subject: [PATCH 0108/1215] improvement: optimize relationships with identity on other end --- lib/aggregate.ex | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index b6a8e256..e2e49f19 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1174,9 +1174,22 @@ defmodule AshPostgres.Aggregate do defp single_path?(resource, [relationship | rest]) do relationship = Ash.Resource.Info.relationship(resource, relationship) - relationship.type == :belongs_to && single_path?(relationship.destination, rest) + + (relationship.type == :belongs_to || + has_one_with_identity?(relationship)) && + single_path?(relationship.destination, rest) + end + + defp has_one_with_identity?(%{type: :has_one} = relationship) do + relationship.destination + |> Ash.Resource.Info.identities() + |> Enum.any?(fn %{keys: keys} -> + keys == [relationship.destination_field] + end) end + defp has_one_with_identity?(_), do: false + @doc false def aggregate_field(aggregate, resource, _relationship_path, query) do case Ash.Resource.Info.field( @@ -1192,10 +1205,6 @@ defmodule AshPostgres.Aggregate do AshPostgres.Expr.validate_type!(query, calc_type, "#{inspect(calculation.name)}") - if aggregate.context == %{} do - raise "what" - end - {:ok, query_calc} = Ash.Query.Calculation.new( calculation.name, From 3fc3740dde6537e6638e6e10606b3f9b79fdefe6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 21 Nov 2023 09:26:02 -0500 Subject: [PATCH 0109/1215] chore: comment out test for now --- test/calculation_test.exs | 118 +++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 9e92eace..42fbf6ce 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -613,63 +613,63 @@ defmodule AshPostgres.CalculationTest do |> Enum.map(&Map.get(&1, :calc_returning_json)) end - test "calculation passes actor to aggregate from calculation on aggregate" do - org = - Organization - |> Ash.Changeset.new(%{name: "The Org"}) - |> Api.create!() - - user = - User - |> Ash.Changeset.for_create(:create, %{is_active: true}) - |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) - |> Api.create!() - - profile = - Profile - |> Ash.Changeset.for_create(:create, %{description: "Prolific describer of worlds..."}) - |> Api.create!() - - author = - Author - |> Ash.Changeset.for_create(:create, %{ - first_name: "Foo", - bio: %{title: "Mr.", bio: "Bones"} - }) - |> Ash.Changeset.manage_relationship(:profile, profile, type: :append) - |> Api.create!() - - created_post = - Post - |> Ash.Changeset.new(%{title: "match"}) - |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) - |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() - - can_get_author_description_post = - Post - |> Ash.Query.filter(id == ^created_post.id) - |> Ash.Query.load(author: :description) - |> Api.read_one!(actor: user) - - assert can_get_author_description_post.author.description == "Prolific describer of worlds..." - - can_get_author_description_from_aggregate_post = - Post - |> Ash.Query.filter(id == ^created_post.id) - |> Ash.Query.load(:author_profile_description) - |> Api.read_one!(actor: user) - - assert can_get_author_description_from_aggregate_post.author_profile_description == - "Prolific describer of worlds..." - - can_get_author_description_from_calculation_of_aggregate_post = - Post - |> Ash.Query.filter(id == ^created_post.id) - |> Ash.Query.load(:author_profile_description_from_agg) - |> Api.read_one!(actor: user) - - assert can_get_author_description_from_calculation_of_aggregate_post.author_profile_description_from_agg == - "Prolific describer of worlds..." - end + # test "calculation passes actor to aggregate from calculation on aggregate" do + # org = + # Organization + # |> Ash.Changeset.new(%{name: "The Org"}) + # |> Api.create!() + + # user = + # User + # |> Ash.Changeset.for_create(:create, %{is_active: true}) + # |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + # |> Api.create!() + + # profile = + # Profile + # |> Ash.Changeset.for_create(:create, %{description: "Prolific describer of worlds..."}) + # |> Api.create!() + + # author = + # Author + # |> Ash.Changeset.for_create(:create, %{ + # first_name: "Foo", + # bio: %{title: "Mr.", bio: "Bones"} + # }) + # |> Ash.Changeset.manage_relationship(:profile, profile, type: :append) + # |> Api.create!() + + # created_post = + # Post + # |> Ash.Changeset.new(%{title: "match"}) + # |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + # |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + # |> Api.create!() + + # can_get_author_description_post = + # Post + # |> Ash.Query.filter(id == ^created_post.id) + # |> Ash.Query.load(author: :description) + # |> Api.read_one!(actor: user) + + # assert can_get_author_description_post.author.description == "Prolific describer of worlds..." + + # can_get_author_description_from_aggregate_post = + # Post + # |> Ash.Query.filter(id == ^created_post.id) + # |> Ash.Query.load(:author_profile_description) + # |> Api.read_one!(actor: user) + + # assert can_get_author_description_from_aggregate_post.author_profile_description == + # "Prolific describer of worlds..." + + # can_get_author_description_from_calculation_of_aggregate_post = + # Post + # |> Ash.Query.filter(id == ^created_post.id) + # |> Ash.Query.load(:author_profile_description_from_agg) + # |> Api.read_one!(actor: user) + + # assert can_get_author_description_from_calculation_of_aggregate_post.author_profile_description_from_agg == + # "Prolific describer of worlds..." + # end end From b133d63edd9f0e3bcf9133ab7370275b2f105e32 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 21 Nov 2023 09:33:13 -0500 Subject: [PATCH 0110/1215] chore: remove unused aliases --- test/calculation_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 42fbf6ce..3cd034f6 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.CalculationTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Account, Api, Author, Comment, Organization, Post, Profile, User} + alias AshPostgres.Test.{Account, Api, Author, Comment, Post, User} require Ash.Query import Ash.Expr From 8e8da855b2e99a50fe34ce206d29e69505ece1dc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 21 Nov 2023 09:44:10 -0500 Subject: [PATCH 0111/1215] chore: fix dialyzer error --- lib/aggregate.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index e2e49f19..4a9867f1 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1184,7 +1184,7 @@ defmodule AshPostgres.Aggregate do relationship.destination |> Ash.Resource.Info.identities() |> Enum.any?(fn %{keys: keys} -> - keys == [relationship.destination_field] + keys == [relationship.destination_attribute] end) end From 179cee4b2452ed451f6d758210b239d915224c18 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 22 Nov 2023 19:37:54 +0100 Subject: [PATCH 0112/1215] chore: add test (#184) --- test/calculation_test.exs | 5 +++++ test/support/resources/author.ex | 2 ++ 2 files changed, 7 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 3cd034f6..1a5cb575 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -58,6 +58,11 @@ defmodule AshPostgres.CalculationTest do Post |> Ash.Query.filter(c_times_p == 6) |> Api.read!() + + assert [] = + Post + |> Ash.Query.filter(author: [has_posts: true]) + |> Api.read!() end test "calculations can refer to to_one path attributes in filters" do diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index f42e6bb3..2304a023 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -92,5 +92,7 @@ defmodule AshPostgres.Test.Author do {AshPostgres.Test.Concat, keys: [:first_name, :last_name]} do argument(:separator, :string, default: " ", constraints: [allow_empty?: true, trim?: false]) end + + calculate :has_posts, :boolean, expr(exists(posts, true)) end end From 5cf9affff41ef1825a09505ac893caa02a338142 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 Nov 2023 14:07:18 -0500 Subject: [PATCH 0113/1215] fix: simplify aggregate bindings & calculation reference building --- lib/aggregate.ex | 27 +++++++--- lib/expr.ex | 131 +++++++---------------------------------------- 2 files changed, 38 insertions(+), 120 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 4a9867f1..55824106 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -111,6 +111,15 @@ defmodule AshPostgres.Aggregate do {:cont, {:ok, query, [{aggregate.load, aggregate.name, exists} | dynamics]}} true -> + root_data_path = + case root_data do + {_, path} -> + path + + _ -> + [] + end + with {:ok, agg_root_query} <- AshPostgres.Join.maybe_get_resource_query( first_relationship.destination, @@ -166,7 +175,8 @@ defmodule AshPostgres.Aggregate do first_relationship, relationship_path, aggregates, - source_binding + source_binding, + root_data_path ) do if select? do new_dynamics = @@ -459,7 +469,8 @@ defmodule AshPostgres.Aggregate do %{manual: {module, opts}} = first_relationship, _relationship_path, aggregates, - source_binding + source_binding, + root_data_path ) do field = first_relationship.destination_attribute @@ -496,7 +507,7 @@ defmodule AshPostgres.Aggregate do AshPostgres.DataLayer.add_binding( query, %{ - path: [], + path: root_data_path, type: :aggregate, aggregates: aggregates } @@ -510,7 +521,8 @@ defmodule AshPostgres.Aggregate do first_relationship, _relationship_path, aggregates, - source_binding + source_binding, + root_data_path ) do join_relationship_struct = Ash.Resource.Info.relationship(source, join_relationship) @@ -562,7 +574,7 @@ defmodule AshPostgres.Aggregate do AshPostgres.DataLayer.add_binding( query, %{ - path: [], + path: root_data_path, type: :aggregate, aggregates: aggregates } @@ -575,7 +587,8 @@ defmodule AshPostgres.Aggregate do first_relationship, _relationship_path, aggregates, - source_binding + source_binding, + root_data_path ) do field = first_relationship.destination_attribute @@ -604,7 +617,7 @@ defmodule AshPostgres.Aggregate do AshPostgres.DataLayer.add_binding( query, %{ - path: [], + path: root_data_path, type: :aggregate, aggregates: aggregates } diff --git a/lib/expr.ex b/lib/expr.ex index 7559bffd..f868f72e 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -765,8 +765,7 @@ defmodule AshPostgres.Expr do query, %Ref{ attribute: %Ash.Query.Calculation{} = calculation, - relationship_path: [], - resource: resource + relationship_path: relationship_path } = type_expr, bindings, embedded?, @@ -781,6 +780,7 @@ defmodule AshPostgres.Expr do ) validate_type!(query, type, type_expr) + resource = Ash.Resource.Info.related(bindings.resource, relationship_path) case Ash.Filter.hydrate_refs( calculation.module.expression(calculation.opts, calculation.context), @@ -792,6 +792,12 @@ defmodule AshPostgres.Expr do } ) do {:ok, expression} -> + expression = + Ash.Filter.move_to_relationship_path( + expression, + relationship_path + ) + expression = Ash.Actions.Read.add_calc_context_to_filter( expression, @@ -840,18 +846,6 @@ defmodule AshPostgres.Expr do ) end - defp do_dynamic_expr( - _query, - %Ref{ - attribute: %Ash.Resource.Calculation{} = calculation - }, - _bindings, - _embedded?, - _type - ) do - raise "cannot build expression from resource calculation! #{calculation.name}" - end - defp do_dynamic_expr( query, %Ref{attribute: %Ash.Query.Aggregate{} = aggregate} = ref, @@ -876,7 +870,12 @@ defmodule AshPostgres.Expr do {ref_binding, field_name, value} = if first_optimized_aggregate? do - ref = %{ref | relationship_path: ref.relationship_path ++ aggregate.relationship_path} + ref = %{ + ref + | attribute: %Ash.Resource.Attribute{name: :fake}, + relationship_path: ref.relationship_path ++ aggregate.relationship_path + } + ref_binding = ref_binding(ref, bindings) if is_nil(ref_binding) do @@ -912,19 +911,14 @@ defmodule AshPostgres.Expr do ref_binding = ref_binding(ref, bindings) if is_nil(ref_binding) do + IO.inspect(ref) + IO.inspect(bindings) raise "Error while building reference: #{inspect(ref)}" end {ref_binding, aggregate.name, nil} end - ref_binding = - if ref.relationship_path == [] || first_optimized_aggregate? do - ref_binding - else - ref_binding + 1 - end - expr = if value do value @@ -964,85 +958,6 @@ defmodule AshPostgres.Expr do end end - defp do_dynamic_expr( - query, - %Ref{ - attribute: %Ash.Query.Calculation{} = calculation, - relationship_path: relationship_path - } = ref, - bindings, - embedded?, - _type - ) do - binding_to_replace = - Enum.find_value(bindings.bindings, fn {i, binding} -> - if binding.path == relationship_path do - i - end - end) - - if is_nil(binding_to_replace) do - raise """ - Error building calculation reference: #{inspect(relationship_path)} is not available in bindings. - - In reference: #{ref} - """ - end - - temp_bindings = - bindings.bindings - |> Map.delete(0) - |> Map.update!(binding_to_replace, &Map.merge(&1, %{path: [], type: :root})) - - type = - AshPostgres.Types.parameterized_type( - calculation.type, - Map.get(calculation, :constraints, []) - ) - - validate_type!(query, type, ref) - - case Ash.Filter.hydrate_refs( - calculation.module.expression(calculation.opts, calculation.context), - %{ - resource: ref.resource, - aggregates: %{}, - calculations: %{}, - public?: false - } - ) do - {:ok, hydrated} -> - hydrated = - Ash.Actions.Read.add_calc_context_to_filter( - hydrated, - calculation.context[:actor], - calculation.context[:authorize?], - calculation.context[:tenant], - calculation.context[:tracer] - ) - - expr = - do_dynamic_expr( - query, - Ash.Filter.update_aggregates(hydrated, fn aggregate, _ -> - %{aggregate | relationship_path: []} - end), - %{bindings | bindings: temp_bindings}, - embedded?, - type - ) - - if type do - Ecto.Query.dynamic(type(^expr, ^type)) - else - expr - end - - _ -> - raise "Failed to hydrate references for #{inspect(ref.resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}" - end - end - defp do_dynamic_expr( query, %Type{arguments: [arg1, arg2, constraints]}, @@ -1511,11 +1426,12 @@ defmodule AshPostgres.Expr do end defp ref_binding( - %{attribute: %Ash.Query.Aggregate{name: name}, relationship_path: []}, + %{attribute: %Ash.Query.Aggregate{name: name}, relationship_path: relationship_path}, bindings ) do Enum.find_value(bindings.bindings, fn {binding, data} -> data.type == :aggregate && + data.path == relationship_path && Enum.any?(data.aggregates, &(&1.name == name)) && binding end) end @@ -1531,17 +1447,6 @@ defmodule AshPostgres.Expr do end) end - defp ref_binding(%{attribute: %Ash.Query.Aggregate{}} = ref, bindings) do - Enum.find_value(bindings.bindings, fn {binding, data} -> - data.type in [:inner, :left, :root] && - Ash.SatSolver.synonymous_relationship_paths?( - bindings.resource, - data.path, - ref.relationship_path - ) && binding - end) - end - defp do_get_path( query, %GetPath{arguments: [left, right], embedded?: pred_embedded?} = get_path, From c53f1733be7bca04965c87a74df431ee080b3bb6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 Nov 2023 14:29:47 -0500 Subject: [PATCH 0114/1215] chore: format --- test/support/resources/author.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 2304a023..54455185 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -93,6 +93,6 @@ defmodule AshPostgres.Test.Author do argument(:separator, :string, default: " ", constraints: [allow_empty?: true, trim?: false]) end - calculate :has_posts, :boolean, expr(exists(posts, true)) + calculate(:has_posts, :boolean, expr(exists(posts, true))) end end From 9c5dd0304e9bf413945f10edd8543f1b61dc0f1d Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 22 Nov 2023 20:43:44 +0100 Subject: [PATCH 0115/1215] chore: remove IO.inspect (#185) --- lib/expr.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index f868f72e..b6cf2743 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -911,8 +911,6 @@ defmodule AshPostgres.Expr do ref_binding = ref_binding(ref, bindings) if is_nil(ref_binding) do - IO.inspect(ref) - IO.inspect(bindings) raise "Error while building reference: #{inspect(ref)}" end From dc984e83c740a520769cd9844296a784032dbda4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 27 Nov 2023 11:12:22 -0500 Subject: [PATCH 0116/1215] fix: avoid empty error on upserts with `:nothing` docs: add some small docs for custom extensions --- lib/data_layer.ex | 2 +- lib/repo.ex | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 9042754a..a8b07cc7 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1219,7 +1219,7 @@ defmodule AshPostgres.DataLayer do upsert_set ) do :empty -> - :nothing + {:replace, options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)} {:ok, query} -> query diff --git a/lib/repo.ex b/lib/repo.ex index 2b01a94e..75517622 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -12,6 +12,9 @@ defmodule AshPostgres.Repo do To configure your list of installed extensions, define `installed_extensions/0` + Extensions can be a string, representing a standard postgres extension, or a module that implements `AshPostgres.CustomExtension`. + That custom extension will be called to generate migrations that serve a specific purpose. + Extensions that are relevant to ash_postgres: * "ash-functions" - This isn't really an extension, but it expresses that certain functions @@ -23,7 +26,7 @@ defmodule AshPostgres.Repo do ``` def installed_extensions() do - ["pg_trgm", "uuid-ossp", "vector"] + ["pg_trgm", "uuid-ossp", "vector", YourCustomExtension] end ``` From 592e79a2f903ec58a58c1eac2d1bc0f33d31ea34 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 27 Nov 2023 17:47:54 -0500 Subject: [PATCH 0117/1215] improvement: support composite types --- lib/expr.ex | 262 +++++++++------ mix.exs | 2 +- mix.lock | 2 +- .../test_repo/posts/20231127215636.json | 303 ++++++++++++++++++ .../20231127212608_add_composite_type.exs | 15 + .../20231127215636_migrate_resources11.exs | 21 ++ test/composite_type_test.exs | 27 ++ test/support/resources/post.ex | 1 + test/support/types/composite_point.ex | 50 +++ 9 files changed, 579 insertions(+), 104 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20231127215636.json create mode 100644 priv/test_repo/migrations/20231127212608_add_composite_type.exs create mode 100644 priv/test_repo/migrations/20231127215636_migrate_resources11.exs create mode 100644 test/composite_type_test.exs create mode 100644 test/support/types/composite_point.ex diff --git a/lib/expr.ex b/lib/expr.ex index b6cf2743..6fc69a18 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -200,48 +200,19 @@ defmodule AshPostgres.Expr do defp do_dynamic_expr( query, %GetPath{ - arguments: [%Ref{attribute: %{type: type}}, right] - } = get_path, - bindings, - embedded?, - nil - ) - when is_atom(type) and is_list(right) do - if Ash.Type.embedded_type?(type) do - type = determine_type_at_path(type, right) - - do_get_path(query, get_path, bindings, embedded?, type) - else - do_get_path(query, get_path, bindings, embedded?) - end - end - - defp do_dynamic_expr( - query, - %GetPath{ - arguments: [%Ref{attribute: %{type: {:array, type}}}, right] - } = get_path, + arguments: [%Ref{attribute: %{type: type, constraints: constraints}} = left, right], + embedded?: pred_embedded? + }, bindings, embedded?, - nil + _ ) - when is_atom(type) and is_list(right) do - if Ash.Type.embedded_type?(type) do - type = determine_type_at_path(type, right) - do_get_path(query, get_path, bindings, embedded?, type) - else - do_get_path(query, get_path, bindings, embedded?) - end - end - - defp do_dynamic_expr( - query, - %GetPath{} = get_path, - bindings, - embedded?, - type - ) do - do_get_path(query, get_path, bindings, embedded?, type) + when is_list(right) do + type + |> split_at_paths(constraints, right) + |> Enum.reduce(do_dynamic_expr(query, left, bindings, embedded?), fn data, expr -> + do_get_path(query, expr, data, bindings, embedded?, pred_embedded?) + end) end defp do_dynamic_expr( @@ -1331,6 +1302,118 @@ defmodule AshPostgres.Expr do end end + defp split_at_paths(type, constraints, next, acc \\ [{:bracket, [], nil, nil}]) + + defp split_at_paths(_type, _constraints, [], acc) do + acc + end + + defp split_at_paths({:array, type}, constraints, [next | rest], [first_acc | rest_acc]) + when is_integer(next) do + case first_acc do + {:bracket, path, nil, nil} -> + split_at_paths(type, constraints[:items] || [], rest, [ + {:bracket, [next | path], type, constraints} + | rest_acc + ]) + + {:dot, _field} -> + split_at_paths(type, constraints[:items] || [], rest, [ + {:bracket, [next], type, constraints}, + first_acc + | rest_acc + ]) + end + end + + defp split_at_paths(type, constraints, [next | rest], [first_acc | rest_acc]) + when is_atom(next) do + bracket_or_dot = + if type && Ash.Type.composite?(type, constraints) do + :dot + else + :bracket + end + + {next, type, constraints} = + cond do + type && Ash.Type.embedded_type?(type) -> + type = + if Ash.Type.NewType.new_type?(type) do + Ash.Type.NewType.subtype_of(type) + else + type + end + + %{type: type, constraints: constraints} = Ash.Resource.Info.attribute(type, next) + {next, type, constraints} + + type && Ash.Type.composite?(type, constraints) -> + condition = + if is_binary(next) do + fn {name, _type, _constraints} -> + to_string(name) == next + end + else + fn {name, _type, _constraints} -> + name == next + end + end + + case Enum.find(Ash.Type.composite_types(type, constraints), condition) do + nil -> + {next, nil, nil} + + {_, aliased_as, type, constraints} -> + {aliased_as, type, constraints} + + {name, type, constraints} -> + {name, type, constraints} + end + + true -> + {next, nil, nil} + end + + case bracket_or_dot do + :dot -> + case first_acc do + {:bracket, [], _, _} -> + split_at_paths(type, constraints, rest, [ + {bracket_or_dot, [next], type, constraints} | rest_acc + ]) + + {:bracket, path, nil, nil} -> + split_at_paths(type, constraints, rest, [ + {bracket_or_dot, [next], type, constraints}, + {:bracket, path, nil, nil} + | rest_acc + ]) + + {:dot, _path} -> + split_at_paths(type, constraints, rest, [ + {bracket_or_dot, [next], nil, nil}, + first_acc | rest_acc + ]) + end + + :bracket -> + case first_acc do + {:bracket, path, nil, nil} -> + split_at_paths(type, constraints, rest, [ + {bracket_or_dot, [next | path], type, constraints} + | rest_acc + ]) + + {:dot, _path} -> + split_at_paths(type, constraints, rest, [ + {bracket_or_dot, [next], nil, nil}, + first_acc | rest_acc + ]) + end + end + end + defp list_expr(query, value, bindings, embedded?, type) do type = case type do @@ -1447,12 +1530,14 @@ defmodule AshPostgres.Expr do defp do_get_path( query, - %GetPath{arguments: [left, right], embedded?: pred_embedded?} = get_path, + expr, + {:bracket, path, type, constraints}, bindings, embedded?, - type \\ nil + pred_embedded? ) do - path = Enum.map(right, &to_string/1) + type = AshPostgres.Types.parameterized_type(type, constraints) + path = path |> Enum.reverse() |> Enum.map(&to_string/1) path_frags = path @@ -1470,7 +1555,7 @@ defmodule AshPostgres.Expr do arguments: [ raw: "jsonb_extract_path_text(", - expr: left, + expr: expr, raw: "::jsonb," ] ++ path_frags }, @@ -1479,8 +1564,39 @@ defmodule AshPostgres.Expr do ) if type do - validate_type!(query, type, get_path) + Ecto.Query.dynamic(type(^expr, ^type)) + else + expr + end + end + + defp do_get_path( + query, + expr, + {:dot, [field], type, constraints}, + bindings, + embedded?, + pred_embedded? + ) + when is_atom(field) do + type = AshPostgres.Types.parameterized_type(type, constraints) + expr = + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "((", + expr: expr, + raw: ").#{field})" + ] + }, + bindings, + embedded? + ) + + if type do Ecto.Query.dynamic(type(^expr, ^type)) else expr @@ -1511,64 +1627,6 @@ defmodule AshPostgres.Expr do end end - defp determine_type_at_path(type, path) do - path - |> Enum.reject(&is_integer/1) - |> do_determine_type_at_path(type) - |> case do - nil -> - nil - - {type, constraints} -> - AshPostgres.Types.parameterized_type(type, constraints) - end - end - - defp do_determine_type_at_path([], _), do: nil - - defp do_determine_type_at_path([item], type) do - case Ash.Resource.Info.attribute(type, item) do - nil -> - nil - - %{type: {:array, type}, constraints: constraints} -> - constraints = constraints[:items] || [] - - {type, constraints} - - %{type: type, constraints: constraints} -> - {type, constraints} - end - end - - defp do_determine_type_at_path([item | rest], type) do - case Ash.Resource.Info.attribute(type, item) do - nil -> - nil - - %{type: {:array, type}} -> - if Ash.Type.embedded_type?(type) do - type - else - nil - end - - %{type: type} -> - if Ash.Type.embedded_type?(type) do - type - else - nil - end - end - |> case do - nil -> - nil - - type -> - do_determine_type_at_path(rest, type) - end - end - @doc false def set_parent_path(query, parent) do # This is a stupid name. Its actually the path we *remove* when stepping up a level. I.e the child's path diff --git a/mix.exs b/mix.exs index 1d3c97f2..283761cc 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.3")}, + {:ash, ash_version("~> 2.17 and >= 2.17.6")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 80212e8e..028644ff 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.3", "b87448baa52360d3a8934526bb9e6c29cbbc0fa0047884a8d5616fda7ee263a4", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "03d310f44240d8c47e7b405afcc8d87b290dac702698d1b470374ba54ae45c49"}, + "ash": {:hex, :ash, "2.17.6", "fcc2512f8245673674a839bc4221b047b88234e5c4ad56e36501d6848a59df5b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d5e69ba8742684299078c77b90ee82f3bd198d96ca232e8ca03e4f91c1f598ab"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/priv/resource_snapshots/test_repo/posts/20231127215636.json b/priv/resource_snapshots/test_repo/posts/20231127215636.json new file mode 100644 index 00000000..66603c06 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20231127215636.json @@ -0,0 +1,303 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + } + ], + "table": "posts", + "hash": "1B8EBBAE5CAF8BA82B5406A3385BA2C934791C1749B1782F5C51E61FEF63E729", + "repo": "Elixir.AshPostgres.TestRepo", + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "unique": true, + "concurrently": true, + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20231127212608_add_composite_type.exs b/priv/test_repo/migrations/20231127212608_add_composite_type.exs new file mode 100644 index 00000000..9f1217ff --- /dev/null +++ b/priv/test_repo/migrations/20231127212608_add_composite_type.exs @@ -0,0 +1,15 @@ +defmodule AshPostgres.TestRepo.Migrations.AddCompositeType do + use Ecto.Migration + + def change do + execute(""" + CREATE TYPE custom_point AS ( + x bigint, + y bigint + ); + """, + """ + DROP TYPE custom_point; + """) + end +end diff --git a/priv/test_repo/migrations/20231127215636_migrate_resources11.exs b/priv/test_repo/migrations/20231127215636_migrate_resources11.exs new file mode 100644 index 00000000..06eb1c5b --- /dev/null +++ b/priv/test_repo/migrations/20231127215636_migrate_resources11.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources11 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(:posts) do + add :composite_point, :custom_point + end + end + + def down do + alter table(:posts) do + remove :composite_point + end + end +end diff --git a/test/composite_type_test.exs b/test/composite_type_test.exs new file mode 100644 index 00000000..3e96c284 --- /dev/null +++ b/test/composite_type_test.exs @@ -0,0 +1,27 @@ +defmodule AshPostgres.Test.CompositeTypeTest do + use AshPostgres.RepoCase + alias AshPostgres.Test.{Api, Post} + require Ash.Query + + test "can be cast and stored" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) + |> Api.create!() + + assert post.composite_point.x == 1 + assert post.composite_point.y == 2 + end + + test "can be referenced in expressions" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) + |> Api.create!() + + post_id = post.id + + assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Api.read_one!() + refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Api.read_one!() + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6195a000..227b4beb 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -85,6 +85,7 @@ defmodule AshPostgres.Test.Post do attribute(:status_enum, AshPostgres.Test.Types.StatusEnum) attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast, source: :status_enum) attribute(:point, AshPostgres.Test.Point) + attribute(:composite_point, AshPostgres.Test.CompositePoint) attribute(:stuff, :map) attribute(:uniq_one, :string) attribute(:uniq_two, :string) diff --git a/test/support/types/composite_point.ex b/test/support/types/composite_point.ex new file mode 100644 index 00000000..6b6f37dc --- /dev/null +++ b/test/support/types/composite_point.ex @@ -0,0 +1,50 @@ +defmodule AshPostgres.Test.CompositePoint do + @moduledoc false + use Ash.Type + + def storage_type(_), do: :custom_point + + def composite?(_constraints) do + true + end + + def composite_types(_constraints) do + [{:x, :integer, []}, {:y, :integer, []}] + end + + def cast_input(nil, _), do: {:ok, nil} + + def cast_input(%{x: a, y: b}, _) when is_integer(a) and is_integer(b) do + {:ok, %{x: a, y: b}} + end + + def cast_input({a, b}, _) when is_integer(a) and is_integer(b) do + {:ok, %{x: a, y: b}} + end + + def cast_input(_, _), do: :error + + def cast_stored(nil, _), do: {:ok, nil} + + def cast_stored(%{x: a, y: b}, _) when is_integer(a) and is_integer(b) do + {:ok, %{x: a, y: b}} + end + + def cast_stored({a, b}, _) when is_integer(a) and is_integer(b) do + {:ok, %{x: a, y: b}} + end + + def cast_stored(_, _) do + :error + end + + def dump_to_native(nil, _), do: {:ok, nil} + + def dump_to_native(%{x: a, y: b}, _) when is_integer(a) and is_integer(b) do + {:ok, {a, b}} + end + + def dump_to_native(_, _) do + :error + end +end From 8d482674a0150dc9131654ad785032e4673009e0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Nov 2023 09:14:57 -0500 Subject: [PATCH 0118/1215] chore: generate migrations --- .../test_repo/posts/20231129141453.json | 303 ++++++++++++++++++ .../20231129141453_migrate_resources12.exs | 21 ++ 2 files changed, 324 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20231129141453.json create mode 100644 priv/test_repo/migrations/20231129141453_migrate_resources12.exs diff --git a/priv/resource_snapshots/test_repo/posts/20231129141453.json b/priv/resource_snapshots/test_repo/posts/20231129141453.json new file mode 100644 index 00000000..a0414b14 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20231129141453.json @@ -0,0 +1,303 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "9D95D490FF85889C844693CE5FF7E6A55164F41554ED17ACD99D9D4684DFFD2A", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "unique": true, + "concurrently": true, + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20231129141453_migrate_resources12.exs b/priv/test_repo/migrations/20231129141453_migrate_resources12.exs new file mode 100644 index 00000000..1fbcbd28 --- /dev/null +++ b/priv/test_repo/migrations/20231129141453_migrate_resources12.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources12 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(:posts) do + modify :composite_point, :custom_point + end + end + + def down do + alter table(:posts) do + modify :composite_point, :point + end + end +end \ No newline at end of file From 47e378e1962dbbc53f5d0e6977cda4b10b21d31f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Nov 2023 09:22:51 -0500 Subject: [PATCH 0119/1215] chore: fix dialyzer --- lib/expr.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 6fc69a18..e9bd3311 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1317,7 +1317,7 @@ defmodule AshPostgres.Expr do | rest_acc ]) - {:dot, _field} -> + {:dot, _field, _, _} -> split_at_paths(type, constraints[:items] || [], rest, [ {:bracket, [next], type, constraints}, first_acc @@ -1390,7 +1390,7 @@ defmodule AshPostgres.Expr do | rest_acc ]) - {:dot, _path} -> + {:dot, _path, _, _} -> split_at_paths(type, constraints, rest, [ {bracket_or_dot, [next], nil, nil}, first_acc | rest_acc @@ -1405,7 +1405,7 @@ defmodule AshPostgres.Expr do | rest_acc ]) - {:dot, _path} -> + {:dot, _path, _, _} -> split_at_paths(type, constraints, rest, [ {bracket_or_dot, [next], nil, nil}, first_acc | rest_acc From 74ef0aa6c36c63d997b2feaf2a3125c764b46d2f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Nov 2023 09:57:10 -0500 Subject: [PATCH 0120/1215] improvement: support `composite_type/2` expression --- lib/expr.ex | 42 ++++++++++++++++++++++++++++++++++ mix.exs | 2 +- mix.lock | 2 +- test/composite_type_test.exs | 11 +++++++++ test/support/resources/post.ex | 6 +++++ 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index e9bd3311..332bf612 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -8,6 +8,7 @@ defmodule AshPostgres.Expr do alias Ash.Query.Function.{ Ago, At, + CompositeType, Contains, DateAdd, DateTimeAdd, @@ -945,6 +946,47 @@ defmodule AshPostgres.Expr do end end + defp do_dynamic_expr( + query, + %CompositeType{arguments: [arg1, arg2, constraints], embedded?: pred_embedded?}, + bindings, + embedded?, + _type + ) + when is_tuple(arg1) do + type = Ash.Type.get_type(arg2) + + type = AshPostgres.Types.parameterized_type(type, constraints) + + values = + arg1 + |> Tuple.to_list() + |> Enum.map(fn value -> + {:expr, value} + end) + |> Enum.intersperse({:raw, ","}) + + # ROW(?, ?)::money_with_currency + + frag = + %Fragment{ + embedded?: pred_embedded?, + arguments: + [ + raw: "ROW(" + ] ++ + values ++ + [ + raw: ")" + ] + } + + frag = + do_dynamic_expr(query, frag, bindings, embedded?) + + Ecto.Query.dynamic(type(^frag, ^type)) + end + defp do_dynamic_expr( query, %Now{embedded?: pred_embedded?}, diff --git a/mix.exs b/mix.exs index 283761cc..1b81bd27 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.6")}, + {:ash, ash_version("~> 2.17 and >= 2.17.7")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 028644ff..a1dc4504 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.6", "fcc2512f8245673674a839bc4221b047b88234e5c4ad56e36501d6848a59df5b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d5e69ba8742684299078c77b90ee82f3bd198d96ca232e8ca03e4f91c1f598ab"}, + "ash": {:hex, :ash, "2.17.7", "8d8f359db61b8ed8245347d836f8a981e69fea769759c69468b5331b342f2308", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd18cf96a245fed88b7bc1d715ab380aafb502e05bf7bc02e7f8200770d4335d"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/composite_type_test.exs b/test/composite_type_test.exs index 3e96c284..4f7e6a95 100644 --- a/test/composite_type_test.exs +++ b/test/composite_type_test.exs @@ -24,4 +24,15 @@ defmodule AshPostgres.Test.CompositeTypeTest do assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Api.read_one!() refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Api.read_one!() end + + test "composite types can be constructed" do + Post + |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) + |> Api.create!() + + assert %{composite_origin: %{x: 0, y: 0}} = + Post + |> Ash.Query.load(:composite_origin) + |> Api.read_one!() + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 227b4beb..b32f3b1a 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -163,6 +163,12 @@ defmodule AshPostgres.Test.Post do calculate(:score_with_score, :string, expr(score <> score)) calculate(:foo_bar_from_stuff, :string, expr(stuff[:foo][:bar])) + calculate( + :composite_origin, + AshPostgres.Test.CompositePoint, + expr(composite_type({0, 0}, AshPostgres.Test.CompositePoint)) + ) + calculate( :score_map, :map, From 07499b6ec0c736b8c0a420d7ac245ac47863450e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Nov 2023 10:27:10 -0500 Subject: [PATCH 0121/1215] fix: use maps for composite_type instead of tuples --- lib/expr.ex | 14 +++++++------- test/support/resources/post.ex | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 332bf612..5fa6b7bf 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -953,21 +953,21 @@ defmodule AshPostgres.Expr do embedded?, _type ) - when is_tuple(arg1) do + when is_map(arg1) do type = Ash.Type.get_type(arg2) + composite_keys = Ash.Type.composite_types(type, constraints) + type = AshPostgres.Types.parameterized_type(type, constraints) values = - arg1 - |> Tuple.to_list() - |> Enum.map(fn value -> - {:expr, value} + composite_keys + |> Enum.map(fn config -> + key = elem(config, 0) + {:expr, Map.get(arg1, key)} end) |> Enum.intersperse({:raw, ","}) - # ROW(?, ?)::money_with_currency - frag = %Fragment{ embedded?: pred_embedded?, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index b32f3b1a..c1c1127e 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -166,7 +166,7 @@ defmodule AshPostgres.Test.Post do calculate( :composite_origin, AshPostgres.Test.CompositePoint, - expr(composite_type({0, 0}, AshPostgres.Test.CompositePoint)) + expr(composite_type(%{x: 0, y: 0}, AshPostgres.Test.CompositePoint)) ) calculate( From ccdbcd00b24d10061ab3b6244bfa3839b0f372b1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 2 Dec 2023 22:31:43 -0500 Subject: [PATCH 0122/1215] chore: release version v1.3.63 --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e832662..2a87f226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,47 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.63](https://github.com/ash-project/ash_postgres/compare/v1.3.62...v1.3.63) (2023-12-03) + + + + +### Bug Fixes: + +* use maps for composite_type instead of tuples + +* avoid empty error on upserts with `:nothing` + +* simplify aggregate bindings & calculation reference building + +* hydrate aggregate refs when adding for calculations + +* apply limit to `from_many?` relationship joins + +* properly add filters for exists aggregates + +* properly expand calculation values across aggregate invocations + +* don't add filter for `no_attributes?` relationships + +* handle `no_attributes?` flag on aggregates better + +* properly handle sorted relationships in aggregates + +### Improvements: + +* support `composite_type/2` expression + +* support composite types + +* optimize relationships with identity on other end + +* allow specifying multi-column foreign keys (#180) + +* add match_with option on references + +* add match_type option on references + ## [v1.3.62](https://github.com/ash-project/ash_postgres/compare/v1.3.61...v1.3.62) (2023-11-16) diff --git a/mix.exs b/mix.exs index 1b81bd27..d2006b8c 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.62" + @version "1.3.63" def project do [ From 51f51d92d2cefaeb1c3c0ce004cb9b3810735c21 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Dec 2023 14:55:40 -0500 Subject: [PATCH 0123/1215] fix: properly cast lazy update defaults to target type --- lib/data_layer.ex | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a8b07cc7..2d3dd9de 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -424,6 +424,7 @@ defmodule AshPostgres.DataLayer do def can?(_, :async_engine), do: true def can?(_, :bulk_create), do: true def can?(_, {:lock, :for_update}), do: true + def can?(_, :composite_types), do: true def can?(_, {:lock, string}) do string = String.trim_trailing(string, " NOWAIT") @@ -1951,6 +1952,9 @@ defmodule AshPostgres.DataLayer do apply(m, f, a) end + {:ok, default_value} = + Ash.Type.cast_input(attribute.type, default_value, attribute.constraints) + {attribute.name, default_value} end) end @@ -1958,8 +1962,8 @@ defmodule AshPostgres.DataLayer do defp lazy_matching_defaults(attributes) do attributes |> Enum.filter(&(&1.match_other_defaults? && get_default_fun(&1))) - |> Enum.group_by(& &1.update_default) - |> Enum.flat_map(fn {default_fun, attributes} -> + |> Enum.group_by(&{&1.update_default, &1.type, &1.constraints}) + |> Enum.flat_map(fn {{default_fun, type, constraints}, attributes} -> default_value = case default_fun do function when is_function(function) -> @@ -1969,6 +1973,9 @@ defmodule AshPostgres.DataLayer do apply(m, f, a) end + {:ok, default_value} = + Ash.Type.cast_input(type, default_value, constraints) + Enum.map(attributes, &{&1.name, default_value}) end) end From 554886f0a3f0f3de23f4393dc2af6b904fceb2df Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Dec 2023 14:56:05 -0500 Subject: [PATCH 0124/1215] chore: release version v1.3.64 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a87f226..93ec57d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.64](https://github.com/ash-project/ash_postgres/compare/v1.3.63...v1.3.64) (2023-12-04) + + + + +### Bug Fixes: + +* properly cast lazy update defaults to target type + ## [v1.3.63](https://github.com/ash-project/ash_postgres/compare/v1.3.62...v1.3.63) (2023-12-03) diff --git a/mix.exs b/mix.exs index d2006b8c..e1506cb5 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.63" + @version "1.3.64" def project do [ From 54908395ebcd8e9a5f8e7e30c1cee3f57e233448 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 6 Dec 2023 18:53:37 -0500 Subject: [PATCH 0125/1215] fix: reenable mix tasks that need calling --- lib/mix/tasks/ash_postgres.create.ex | 1 + lib/mix/tasks/ash_postgres.drop.ex | 1 + lib/mix/tasks/ash_postgres.migrate.ex | 2 ++ lib/mix/tasks/ash_postgres.rollback.ex | 2 ++ 4 files changed, 6 insertions(+) diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index 976aa21d..bdfaa614 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -43,6 +43,7 @@ defmodule Mix.Tasks.AshPostgres.Create do rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis") + Mix.Task.reenable("ecto.create") Mix.Task.run("ecto.create", repo_args ++ rest_opts) end end diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 8234bfbe..af121b0b 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -53,6 +53,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis") + Mix.Task.reenable("ecto.drop") Mix.Task.run("ecto.drop", repo_args ++ rest_opts) end end diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index abd6036a..02d14d7d 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -118,6 +118,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do |> AshPostgres.MixHelpers.delete_flag("--only-tenants") |> AshPostgres.MixHelpers.delete_flag("--except-tenants") + Mix.Task.reenable("ecto.migrate") + if opts[:tenants] do for repo <- repos do Ecto.Migrator.with_repo(repo, fn repo -> diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index 68e4878e..973d958d 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -77,6 +77,8 @@ defmodule Mix.Tasks.AshPostgres.Rollback do |> AshPostgres.MixHelpers.delete_flag("--only-tenants") |> AshPostgres.MixHelpers.delete_flag("--except-tenants") + Mix.Task.reenable("ecto.rollback") + if opts[:tenants] do for repo <- repos do Ecto.Migrator.with_repo(repo, fn repo -> From 407a7163ede2f4e74ebff83191ed734106ae1b6b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 14 Dec 2023 17:10:11 -0500 Subject: [PATCH 0126/1215] improvement: support for `error/2` expression --- lib/data_layer.ex | 254 ++++++++++++------ lib/expr.ex | 32 +++ lib/migration_generator/ash_functions.ex | 162 +++++++++++ .../migration_generator.ex | 147 ++-------- mix.exs | 2 +- mix.lock | 6 +- priv/resource_snapshots/extensions.json | 4 +- ...0937_install_ash-functions_extension_2.exs | 43 +++ test/error_expr_test.ex | 38 +++ 9 files changed, 468 insertions(+), 220 deletions(-) create mode 100644 lib/migration_generator/ash_functions.ex create mode 100644 priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs create mode 100644 test/error_expr_test.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2d3dd9de..2159c909 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -645,7 +645,11 @@ defmodule AshPostgres.DataLayer do if AshPostgres.DataLayer.Info.polymorphic?(resource) && no_table?(query) do raise_table_error!(resource, :read) else - {:ok, dynamic_repo(resource, query).all(query, repo_opts(nil, nil, resource))} + repo = dynamic_repo(resource, query) + + with_savepoint(repo, fn -> + {:ok, repo.all(query, repo_opts(nil, nil, resource))} + end) end end rescue @@ -1195,100 +1199,138 @@ defmodule AshPostgres.DataLayer do changesets = Enum.to_list(stream) - opts = - if options[:upsert?] do - # Ash groups changesets by atomics before dispatching them to the data layer - # this means that all changesets have the same atomics - %{atomics: atomics, filters: filters} = Enum.at(changesets, 0) + repo = dynamic_repo(resource, Enum.at(changesets, 0)) - query = from(row in resource, as: ^0) + try do + opts = + if options[:upsert?] do + # Ash groups changesets by atomics before dispatching them to the data layer + # this means that all changesets have the same atomics + %{atomics: atomics, filters: filters} = Enum.at(changesets, 0) - query = - query - |> default_bindings(resource) + query = from(row in resource, as: ^0) - upsert_set = - upsert_set(resource, changesets, options) + query = + query + |> default_bindings(resource) - on_conflict = - case query_with_atomics( - resource, - query, - filters, - atomics, - %{}, - upsert_set - ) do - :empty -> - {:replace, options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)} - - {:ok, query} -> - query - - {:error, error} -> - raise Ash.Error.to_ash_error(error) - end + upsert_set = + upsert_set(resource, changesets, options) + + on_conflict = + case query_with_atomics( + resource, + query, + filters, + atomics, + %{}, + upsert_set + ) do + :empty -> + {:replace, options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)} + + {:ok, query} -> + query - opts - |> Keyword.put(:on_conflict, on_conflict) - |> Keyword.put( - :conflict_target, - conflict_target( - resource, - options[:upsert_keys] || Ash.Resource.Info.primary_key(resource) + {:error, error} -> + raise Ash.Error.to_ash_error(error) + end + + opts + |> Keyword.put(:on_conflict, on_conflict) + |> Keyword.put( + :conflict_target, + conflict_target( + resource, + options[:upsert_keys] || Ash.Resource.Info.primary_key(resource) + ) ) - ) - else - opts - end + else + opts + end - ecto_changesets = Enum.map(changesets, & &1.attributes) + ecto_changesets = Enum.map(changesets, & &1.attributes) - source = - if table = Enum.at(changesets, 0).context[:data_layer][:table] do - {table, resource} - else - resource + source = + if table = Enum.at(changesets, 0).context[:data_layer][:table] do + {table, resource} + else + resource + end + + result = + with_savepoint(repo, fn -> + repo.insert_all(source, ecto_changesets, opts) + end) + + case result do + {_, nil} -> + :ok + + {_, results} -> + if options[:single?] do + Enum.each(results, &maybe_create_tenant!(resource, &1)) + + {:ok, results} + else + {:ok, + Stream.zip_with(results, changesets, fn result, changeset -> + if !opts[:upsert?] do + maybe_create_tenant!(resource, result) + end + + Ash.Resource.put_metadata( + result, + :bulk_create_index, + changeset.context.bulk_create.index + ) + end)} + end end + rescue + e -> + changeset = Ash.Changeset.new(resource) - repo = dynamic_repo(resource, Enum.at(changesets, 0)) + handle_raised_error( + e, + __STACKTRACE__, + {:bulk_create, ecto_changeset(changeset.data, changeset, :create, false)}, + resource + ) + end + end - source - |> repo.insert_all(ecto_changesets, opts) - |> case do - {_, nil} -> - :ok + defp with_savepoint(repo, fun) do + if repo.in_transaction?() do + savepoint_id = "a" <> (Ash.UUID.generate() |> String.replace("-", "_")) - {_, results} -> - if options[:single?] do - Enum.each(results, &maybe_create_tenant!(resource, &1)) + repo.query!("SAVEPOINT #{savepoint_id}") - {:ok, results} - else - {:ok, - Stream.zip_with(results, changesets, fn result, changeset -> - if !opts[:upsert?] do - maybe_create_tenant!(resource, result) - end - - Ash.Resource.put_metadata( - result, - :bulk_create_index, - changeset.context.bulk_create.index - ) - end)} + result = + try do + {:ok, fun.()} + rescue + e -> + repo.query!("ROLLBACK TO #{savepoint_id}") + {:exception, e, __STACKTRACE__} end - end - rescue - e -> - changeset = Ash.Changeset.new(resource) - handle_raised_error( - e, - __STACKTRACE__, - {:bulk_create, ecto_changeset(changeset.data, changeset, :create, false)}, - resource - ) + case result do + {:exception, e, stacktrace} -> + reraise e, stacktrace + + {:ok, result} -> + repo.query!("RELEASE #{savepoint_id}") + result + end + else + try do + fun.() + rescue + e -> + reraise e, __STACKTRACE__ + end + end end defp upsert_set(resource, changesets, options) do @@ -1583,6 +1625,29 @@ defmodule AshPostgres.DataLayer do end end + defp handle_raised_error( + %Postgrex.Error{ + postgres: %{ + code: :raise_exception, + message: "\"ash_exception: " <> json, + severity: "ERROR" + } + }, + _, + _, + _ + ) do + %{"exception" => exception, "input" => input} = + json + |> String.trim_trailing("\"") + |> String.replace("\\\"", "\"") + |> Jason.decode!() + + exception = Module.concat([exception]) + + {:error, Ash.Error.from_json(exception, input)} + end + defp handle_raised_error(error, stacktrace, _ecto_changeset, _resource) do {:error, Ash.Error.to_ash_error(error, stacktrace)} end @@ -2020,12 +2085,16 @@ defmodule AshPostgres.DataLayer do repo_opts = Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + repo = dynamic_repo(resource, changeset) + result = - dynamic_repo(resource, changeset).update_all( - query, - [], - repo_opts - ) + with_savepoint(repo, fn -> + repo.update_all( + query, + [], + repo_opts + ) + end) case result do {0, []} -> @@ -2174,10 +2243,17 @@ defmodule AshPostgres.DataLayer do ecto_changeset = ecto_changeset(record, changeset, :delete) try do - ecto_changeset - |> dynamic_repo(resource, changeset).delete( - repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - ) + repo = dynamic_repo(resource, changeset) + + result = + with_savepoint(repo, fn -> + repo.delete( + ecto_changeset, + repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + ) + end) + + result |> from_ecto() |> case do {:ok, _record} -> diff --git a/lib/expr.ex b/lib/expr.ex index 5fa6b7bf..2fb47a12 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -17,6 +17,7 @@ defmodule AshPostgres.Expr do If, Length, Now, + Error, StringJoin, StringSplit, Today, @@ -1041,6 +1042,37 @@ defmodule AshPostgres.Expr do ) end + defp do_dynamic_expr( + query, + %Error{arguments: [exception, input]} = value, + _bindings, + _embedded?, + type + ) do + require_ash_functions!(query, "error/2") + + unless Keyword.keyword?(input) || is_map(input) do + raise "Input expression to `error` must be a map or keyword list" + end + + encoded = + "ash_exception: " <> Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}) + + if type do + # This is a type hint, if we're raising an error, we tell it what the value + # type *would* be in this expression so that we can return a "NULL" of that type + # its weird, but there isn't any other way that I can tell :) + validate_type!(query, type, value) + + dynamic = + Ecto.Query.dynamic(type(^nil, ^type)) + + Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb, ?)", ^encoded, ^dynamic)) + else + Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb)", ^encoded)) + end + end + defp do_dynamic_expr( query, %Exists{at_path: at_path, path: [first | rest], expr: expr}, diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex new file mode 100644 index 00000000..ca6c695f --- /dev/null +++ b/lib/migration_generator/ash_functions.ex @@ -0,0 +1,162 @@ +defmodule AshPostgres.MigrationGenerator.AshFunctions do + @latest_version 2 + + def latest_version, do: @latest_version + + @moduledoc false + def install(nil) do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ + LANGUAGE SQL + IMMUTABLE; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE($1, $2) $$ + LANGUAGE SQL + IMMUTABLE; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS TRUE THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS NOT NULL THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) + RETURNS text[] AS $$ + DECLARE + start_index INT = 1; + end_index INT = array_length(arr, 1); + BEGIN + WHILE start_index <= end_index AND arr[start_index] = '' LOOP + start_index := start_index + 1; + END LOOP; + + WHILE end_index >= start_index AND arr[end_index] = '' LOOP + end_index := end_index - 1; + END LOOP; + + IF start_index > end_index THEN + RETURN ARRAY[]::text[]; + ELSE + RETURN arr[start_index : end_index]; + END IF; + END; $$ + LANGUAGE plpgsql + IMMUTABLE; + \"\"\") + + #{ash_raise_error()} + """ + end + + def install(0) do + """ + execute(\"\"\" + ALTER FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE + \"\"\") + + execute(\"\"\" + ALTER FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE + \"\"\") + + execute(\"\"\" + ALTER FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE + \"\"\") + + execute(\"\"\" + ALTER FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE + \"\"\") + + #{ash_raise_error()} + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) + RETURNS text[] AS $$ + DECLARE + start_index INT = 1; + end_index INT = array_length(arr, 1); + BEGIN + WHILE start_index <= end_index AND arr[start_index] = '' LOOP + start_index := start_index + 1; + END LOOP; + + WHILE end_index >= start_index AND arr[end_index] = '' LOOP + end_index := end_index - 1; + END LOOP; + + IF start_index > end_index THEN + RETURN ARRAY[]::text[]; + ELSE + RETURN arr[start_index : end_index]; + END IF; + END; $$ + LANGUAGE plpgsql + IMMUTABLE; + \"\"\") + """ + end + + def install(1) do + ash_raise_error() + end + + def drop(1) do + "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)\")" + end + + def drop(0) do + "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" + end + + def drop(nil) do + "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE) ash_trim_whitespace(text[])\")" + end + + defp ash_raise_error do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION '%', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data json, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION '%', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + \"\"\") + """ + end +end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 721f9ea7..ef3d0389 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -153,8 +153,6 @@ defmodule AshPostgres.MigrationGenerator do Path.join([Mix.Project.deps_paths()[app] || File.cwd!(), "priv", "resource_snapshots"]) end - @latest_ash_functions_version 1 - defp create_extension_migrations(repos, opts) do for repo <- repos do snapshot_path = snapshot_path(opts, repo) @@ -194,17 +192,15 @@ defmodule AshPostgres.MigrationGenerator do to_install = requesteds - |> Enum.filter(fn {name, _extension} -> !Enum.member?(installed_extensions, name) end) - |> Enum.map(fn {_name, extension} -> extension end) + |> Enum.reject(fn + {"ash-functions", _} -> + extensions_snapshot[:ash_functions_version] == + AshPostgres.MigrationGenerator.AshFunctions.latest_version() - to_install = - if "ash-functions" in requesteds && - extensions_snapshot[:ash_functions_version] != - @latest_ash_functions_version do - Enum.uniq(["ash-functions" | to_install]) - else - to_install - end + {name, _} -> + Enum.member?(installed_extensions, name) + end) + |> Enum.map(fn {_name, extension} -> extension end) if Enum.empty?(to_install) do Mix.shell().info("No extensions to install") @@ -216,8 +212,9 @@ defmodule AshPostgres.MigrationGenerator do {"install_#{ext_name}_v#{version}", "#{timestamp(true)}_install_#{ext_name}_v#{version}_extension"} - [single] -> - {"install_#{single}", "#{timestamp(true)}_install_#{single}_extension"} + ["ash-functions" = single] -> + {"install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}", + "#{timestamp(true)}_install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}"} multiple -> {"install_#{Enum.count(multiple)}_extensions", @@ -239,7 +236,9 @@ defmodule AshPostgres.MigrationGenerator do install = Enum.map_join(to_install, "\n", fn "ash-functions" -> - install_ash_functions(extensions_snapshot[:ash_functions_version]) + AshPostgres.MigrationGenerator.AshFunctions.install( + extensions_snapshot[:ash_functions_version] + ) {_ext_name, version, up_fn, _down_fn} when is_function(up_fn, 1) -> up_fn.(version) @@ -251,7 +250,9 @@ defmodule AshPostgres.MigrationGenerator do uninstall = Enum.map_join(to_install, "\n", fn "ash-functions" -> - "execute(\"DROP FUNCTION IF EXISTS ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE)\")" + AshPostgres.MigrationGenerator.AshFunctions.drop( + extensions_snapshot[:ash_functions_version] + ) {_ext_name, version, _up_fn, down_fn} when is_function(down_fn, 1) -> down_fn.(version) @@ -300,117 +301,13 @@ defmodule AshPostgres.MigrationGenerator do end end - defp install_ash_functions(nil) do - """ - execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) - AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ - LANGUAGE SQL - IMMUTABLE; - \"\"\") - - execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) - AS $$ SELECT COALESCE($1, $2) $$ - LANGUAGE SQL - IMMUTABLE; - \"\"\") - - execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ - SELECT CASE - WHEN $1 IS TRUE THEN $2 - ELSE $1 - END $$ - LANGUAGE SQL - IMMUTABLE; - \"\"\") - - execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ - SELECT CASE - WHEN $1 IS NOT NULL THEN $2 - ELSE $1 - END $$ - LANGUAGE SQL - IMMUTABLE; - \"\"\") - - execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) - RETURNS text[] AS $$ - DECLARE - start_index INT = 1; - end_index INT = array_length(arr, 1); - BEGIN - WHILE start_index <= end_index AND arr[start_index] = '' LOOP - start_index := start_index + 1; - END LOOP; - - WHILE end_index >= start_index AND arr[end_index] = '' LOOP - end_index := end_index - 1; - END LOOP; - - IF start_index > end_index THEN - RETURN ARRAY[]::text[]; - ELSE - RETURN arr[start_index : end_index]; - END IF; - END; $$ - LANGUAGE plpgsql - IMMUTABLE; - \"\"\") - """ - end - - defp install_ash_functions(0) do - """ - execute(\"\"\" - ALTER FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE - \"\"\") - - execute(\"\"\" - ALTER FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE - \"\"\") - - execute(\"\"\" - ALTER FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE - \"\"\") - - execute(\"\"\" - ALTER FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) IMMUTABLE - \"\"\") - - execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) - RETURNS text[] AS $$ - DECLARE - start_index INT = 1; - end_index INT = array_length(arr, 1); - BEGIN - WHILE start_index <= end_index AND arr[start_index] = '' LOOP - start_index := start_index + 1; - END LOOP; - - WHILE end_index >= start_index AND arr[end_index] = '' LOOP - end_index := end_index - 1; - END LOOP; - - IF start_index > end_index THEN - RETURN ARRAY[]::text[]; - ELSE - RETURN arr[start_index : end_index]; - END IF; - END; $$ - LANGUAGE plpgsql - IMMUTABLE; - \"\"\") - """ - end - defp set_ash_functions(snapshot, installed_extensions) do if "ash-functions" in installed_extensions do - Map.put(snapshot, :ash_functions_version, @latest_ash_functions_version) + Map.put( + snapshot, + :ash_functions_version, + AshPostgres.MigrationGenerator.AshFunctions.latest_version() + ) else snapshot end diff --git a/mix.exs b/mix.exs index e1506cb5..85a1e098 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.7")}, + {:ash, ash_version("~> 2.17 and >= 2.17.13")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index a1dc4504..a36a4d44 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.7", "8d8f359db61b8ed8245347d836f8a981e69fea769759c69468b5331b342f2308", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd18cf96a245fed88b7bc1d715ab380aafb502e05bf7bc02e7f8200770d4335d"}, + "ash": {:hex, :ash, "2.17.13", "a3bb846238d4eb029da00583554f074d73950cfa4bc2f5964283d2e4491a9543", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b41a3e029b1553e71a0cab7db66d98a1ea7a883d301b866630ef9d09e64d38ee"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, @@ -30,14 +30,14 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, - "spark": {:hex, :spark, "1.1.51", "8458de5abbb89d18dd5c9235dd39e3757076eba84a5078d1cdc2c1e23c39aa95", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ed8410aa8db08867b8fff3d65e54deeb7f6f6cf2b8698fc405a386c1c7a9e4f0"}, + "spark": {:hex, :spark, "1.1.52", "e0ddd137899c11fb44ef46cda346a112e60365b93e50264da976f45b1c6e28c5", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "2d8b354103eb4ae5fb4ed5f885d491e3ed5684ccb57806c3980fcc15a4b597d6"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, diff --git a/priv/resource_snapshots/extensions.json b/priv/resource_snapshots/extensions.json index 08e58099..d134b4d9 100644 --- a/priv/resource_snapshots/extensions.json +++ b/priv/resource_snapshots/extensions.json @@ -1,10 +1,10 @@ { - "ash_functions_version": 1, "installed": [ "ash-functions", "uuid-ossp", "pg_trgm", "citext", "demo-functions_v1" - ] + ], + "ash_functions_version": 2 } \ No newline at end of file diff --git a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs new file mode 100644 index 00000000..540d8a67 --- /dev/null +++ b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs @@ -0,0 +1,43 @@ +defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension2 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION '%', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data json, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION '%', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute( + "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)" + ) + end +end \ No newline at end of file diff --git a/test/error_expr_test.ex b/test/error_expr_test.ex new file mode 100644 index 00000000..8e37df00 --- /dev/null +++ b/test/error_expr_test.ex @@ -0,0 +1,38 @@ +defmodule AshPostgres.ErrorExprTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.{Api, Author, Comment, Post} + + require Ash.Query + import Ash.Expr + + test "exceptions in filters are treated as regular Ash exceptions" do + Post + |> Ash.Changeset.new(%{title: "title"}) + |> Api.create!() + + assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> + Post + |> Ash.Query.filter( + error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10) + ) + |> Api.read!() + end + end + + test "exceptions in calculations are treated as regular Ash exceptions" do + Post + |> Ash.Changeset.new(%{title: "title"}) + |> Api.create!() + + assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> + Post + |> Ash.Query.calculate( + :test, + expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), + :string + ) + |> Api.read!() + |> Enum.map(& &1.calculations) + end + end +end From ac980a216395be4424ec4bd3bcc6a41c23c0a210 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 15 Dec 2023 08:48:20 -0500 Subject: [PATCH 0127/1215] chore: fix error expression --- lib/expr.ex | 2 +- lib/migration_generator/ash_functions.ex | 2 +- .../20231214220937_install_ash-functions_extension_2.exs | 4 ++-- test/{error_expr_test.ex => error_expr_test.exs} | 2 +- test_snapshot_path/extensions.json | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) rename test/{error_expr_test.ex => error_expr_test.exs} (94%) diff --git a/lib/expr.ex b/lib/expr.ex index 2fb47a12..7346f0d7 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -12,12 +12,12 @@ defmodule AshPostgres.Expr do Contains, DateAdd, DateTimeAdd, + Error, FromNow, GetPath, If, Length, Now, - Error, StringJoin, StringSplit, Today, diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index ca6c695f..65f29f4b 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -147,7 +147,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do \"\"\") execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_raise_error(json_data json, type_signal ANYCOMPATIBLE) + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) RETURNS ANYCOMPATIBLE AS $$ BEGIN -- Raise an error with the provided JSON data. diff --git a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs index 540d8a67..d3192fdc 100644 --- a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs +++ b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs @@ -21,7 +21,7 @@ defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension2 do """) execute(""" - CREATE OR REPLACE FUNCTION ash_raise_error(json_data json, type_signal ANYCOMPATIBLE) + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) RETURNS ANYCOMPATIBLE AS $$ BEGIN -- Raise an error with the provided JSON data. @@ -40,4 +40,4 @@ defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension2 do "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)" ) end -end \ No newline at end of file +end diff --git a/test/error_expr_test.ex b/test/error_expr_test.exs similarity index 94% rename from test/error_expr_test.ex rename to test/error_expr_test.exs index 8e37df00..ff723018 100644 --- a/test/error_expr_test.ex +++ b/test/error_expr_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.ErrorExprTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Author, Comment, Post} + alias AshPostgres.Test.{Api, Post} require Ash.Query import Ash.Expr diff --git a/test_snapshot_path/extensions.json b/test_snapshot_path/extensions.json index 08e58099..d134b4d9 100644 --- a/test_snapshot_path/extensions.json +++ b/test_snapshot_path/extensions.json @@ -1,10 +1,10 @@ { - "ash_functions_version": 1, "installed": [ "ash-functions", "uuid-ossp", "pg_trgm", "citext", "demo-functions_v1" - ] + ], + "ash_functions_version": 2 } \ No newline at end of file From 5e0f3c4657ffac90a6fc110985a7b1239606b245 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 15 Dec 2023 18:11:18 -0500 Subject: [PATCH 0128/1215] fix: handle strings in get_path --- lib/data_layer.ex | 1 + lib/expr.ex | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2159c909..50e9d23f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -502,6 +502,7 @@ defmodule AshPostgres.DataLayer do def can?(_, {:aggregate_relationship, _}), do: true def can?(_, :timeout), do: true + def can?(_, :expr_error), do: true def can?(_, {:filter_expr, _}), do: true def can?(_, :nested_expressions), do: true def can?(_, {:query_aggregate, _}), do: true diff --git a/lib/expr.ex b/lib/expr.ex index 7346f0d7..a9a152de 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1401,7 +1401,7 @@ defmodule AshPostgres.Expr do end defp split_at_paths(type, constraints, [next | rest], [first_acc | rest_acc]) - when is_atom(next) do + when is_atom(next) or is_binary(next) do bracket_or_dot = if type && Ash.Type.composite?(type, constraints) do :dot From c91603493180254880a0309ff534fa1fc9dce652 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 19 Dec 2023 15:05:05 +0100 Subject: [PATCH 0129/1215] fix: replace upsert field with source in EXCLUDED fragment (#187) --- .../dsls/DSL:-AshPostgres.DataLayer.cheatmd | 1394 ----------------- .../dsls/DSL:-AshPostgres.DataLayer.md | 387 +++++ lib/data_layer.ex | 17 +- .../test_repo/posts/20231219132807.json | 303 ++++ .../20231219132807_migrate_resources13.exs | 17 + test/support/resources/post.ex | 6 +- 6 files changed, 727 insertions(+), 1397 deletions(-) delete mode 100644 documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd create mode 100644 documentation/dsls/DSL:-AshPostgres.DataLayer.md create mode 100644 priv/resource_snapshots/test_repo/posts/20231219132807.json create mode 100644 priv/test_repo/migrations/20231219132807_migrate_resources13.exs diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd b/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd deleted file mode 100644 index 6e007731..00000000 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.cheatmd +++ /dev/null @@ -1,1394 +0,0 @@ - -# DSL: AshPostgres.DataLayer - -A postgres data layer that leverages Ecto's postgres capabilities. - - -## postgres -Postgres data layer configuration - - -### Nested DSLs - * [custom_indexes](#postgres-custom_indexes) - * index - * [custom_statements](#postgres-custom_statements) - * statement - * [manage_tenant](#postgres-manage_tenant) - * [references](#postgres-references) - * reference - * [check_constraints](#postgres-check_constraints) - * check_constraint - - -### Examples -``` -postgres do - repo MyApp.Repo - table "organizations" -end - -``` - - - - -### Options - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - repo - - - * - - - module | (any, any -> any) - - - - The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more. Can also be a function that takes a resource and a type `:read | :mutate` and returns the repo -
- - - migrate? - - - - - boolean - - true - - Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` -
- - - migration_types - - - - - Keyword.t - - [] - - A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. -
- - - migration_defaults - - - - - Keyword.t - - [] - - A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. - -
- - - base_filter_sql - - - - - String.t - - - - A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter -
- - - simple_join_first_aggregates - - - - - list(atom) - - [] - - A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. - -
- - - skip_unique_indexes - - - - - atom | list(atom) - - false - - Skip generating unique indexes when generating migrations -
- - - unique_index_names - - - - - list({list(atom), String.t} | {list(atom), String.t, String.t}) - - [] - - A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` - -
- - - exclusion_constraint_names - - - - - `any` - - [] - - A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` - -
- - - identity_index_names - - - - - `any` - - [] - - A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. - -
- - - foreign_key_names - - - - - list({atom | String.t, String.t} | {atom | String.t, String.t, String.t}) - - [] - - A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` - -
- - - migration_ignore_attributes - - - - - list(atom) - - [] - - A list of attributes that will be ignored when generating migrations. - -
- - - table - - - - - String.t - - - - The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. - -
- - - schema - - - - - String.t - - - - The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. - -
- - - polymorphic? - - - - - boolean - - false - - Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. - -
- - -## postgres.custom_indexes -A section for configuring indexes to be created by the migration generator. - -In general, prefer to use `identities` for simple unique constraints. This is a tool to allow -for declaring more complex indexes. - - -### Nested DSLs - * [index](#postgres-custom_indexes-index) - - -### Examples -``` -custom_indexes do - index [:column1, :column2], unique: true, where: "thing = TRUE" -end - -``` - - - - -## postgres.custom_indexes.index -```elixir -index fields -``` - - -Add an index to be managed by the migration generator. - - - - -### Examples -``` -index ["column", "column2"], unique: true, where: "thing = TRUE" -``` - - - -### Arguments - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - fields - - - - - atom | String.t | list(atom | String.t) - - - - The fields to include in the index. -
-### Options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - name - - - - - String.t - - - - the name of the index. Defaults to "#{table}_#{column}_index". -
- - - unique - - - - - boolean - - false - - indicates whether the index should be unique. -
- - - concurrently - - - - - boolean - - false - - indicates whether the index should be created/dropped concurrently. -
- - - using - - - - - String.t - - - - configures the index type. -
- - - prefix - - - - - String.t - - - - specify an optional prefix for the index. -
- - - where - - - - - String.t - - - - specify conditions for a partial index. -
- - - message - - - - - String.t - - - - A custom message to use for unique indexes that have been violated -
- - - include - - - - - list(String.t) - - - - specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. -
- - - - - -### Introspection - -Target: `AshPostgres.CustomIndex` - - -## postgres.custom_statements -A section for configuring custom statements to be added to migrations. - -Changing custom statements may require manual intervention, because Ash can't determine what order they should run -in (i.e if they depend on table structure that you've added, or vice versa). As such, any `down` statements we run -for custom statements happen first, and any `up` statements happen last. - -Additionally, when changing a custom statement, we must make some assumptions, i.e that we should migrate -the old structure down using the previously configured `down` and recreate it. - -This may not be desired, and so what you may end up doing is simply modifying the old migration and deleting whatever was -generated by the migration generator. As always: read your migrations after generating them! - - -### Nested DSLs - * [statement](#postgres-custom_statements-statement) - - -### Examples -``` -custom_statements do - # the name is used to detect if you remove or modify the statement - statement :pgweb_idx do - up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));" - down "DROP INDEX pgweb_idx;" - end -end - -``` - - - - -## postgres.custom_statements.statement -```elixir -statement name -``` - - -Add a custom statement for migrations. - - - - -### Examples -``` -statement :pgweb_idx do - up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));" - down "DROP INDEX pgweb_idx;" -end - -``` - - - -### Arguments - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - name - - - * - - - atom - - - - The name of the statement, must be unique within the resource - -
-### Options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - up - - - * - - - String.t - - - - How to create the structure of the statement - -
- - - down - - - * - - - String.t - - - - How to tear down the structure of the statement -
- - - code? - - - - - boolean - - false - - By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations - -
- - - - - -### Introspection - -Target: `AshPostgres.Statement` - - -## postgres.manage_tenant -Configuration for the behavior of a resource that manages a tenant - - - - -### Examples -``` -manage_tenant do - template ["organization_", :id] - create? true - update? false -end - -``` - - - - -### Options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - template - - - * - - - String.t | atom | list(String.t | atom) - - - - A template that will cause the resource to create/manage the specified schema. - -
- - - create? - - - - - boolean - - true - - Whether or not to automatically create a tenant when a record is created -
- - - update? - - - - - boolean - - true - - Whether or not to automatically update the tenant name if the record is udpated -
- - - - -## postgres.references -A section for configuring the references (foreign keys) in resource migrations. - -This section is only relevant if you are using the migration generator with this resource. -Otherwise, it has no effect. - - -### Nested DSLs - * [reference](#postgres-references-reference) - - -### Examples -``` -references do - reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" -end - -``` - - - - -### Options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - polymorphic_on_delete - - - - - :delete | :nilify | :nothing | :restrict - - - - For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. -
- - - polymorphic_on_update - - - - - :update | :nilify | :nothing | :restrict - - - - For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. -
- - - polymorphic_name - - - - - :update | :nilify | :nothing | :restrict - - - - For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. -
- - - -## postgres.references.reference -```elixir -reference relationship -``` - - -Configures the reference for a relationship in resource migrations. - -Keep in mind that multiple relationships can theoretically involve the same destination and foreign keys. -In those cases, you only need to configure the `reference` behavior for one of them. Any conflicts will result -in an error, across this resource and any other resources that share a table with this one. For this reason, -instead of adding a reference configuration for `:nothing`, its best to just leave the configuration out, as that -is the default behavior if *no* relationship anywhere has configured the behavior of that reference. - - - - -### Examples -``` -reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" -``` - - - -### Arguments - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - relationship - - - * - - - atom - - - - The relationship to be configured -
-### Options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - ignore? - - - - - boolean - - - - If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way -
- - - on_delete - - - - - :delete | :nilify | :nothing | :restrict - - - - What should happen to records of this resource when the referenced record of the *destination* resource is deleted. - -
- - - on_update - - - - - :update | :nilify | :nothing | :restrict - - - - What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. - -
- - - deferrable - - - - - false | true | :initially - - false - - Wether or not the constraint is deferrable. This only affects the migration generator. - -
- - - name - - - - - String.t - - - - The name of the foreign key to generate in the database. Defaults to __fkey - - - - - - - - - - - - - - - - - - -
- - - match_with - - - - - Keyword.t - - - - Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute. -
- - - match_type - - - - - :simple | :partial | :full - - - - select if the match is `:simple`, `:partial`, or `:full` -
- - - - - -### Introspection - -Target: `AshPostgres.Reference` - - -## postgres.check_constraints -A section for configuring the check constraints for a given table. - -This can be used to automatically create those check constraints, or just to provide message when they are raised - - -### Nested DSLs - * [check_constraint](#postgres-check_constraints-check_constraint) - - -### Examples -``` -check_constraints do - check_constraint :price, "price_must_be_positive", check: "price > 0", message: "price must be positive" -end - -``` - - - - -## postgres.check_constraints.check_constraint -```elixir -check_constraint attribute, name -``` - - -Add a check constraint to be validated. - -If a check constraint exists on the table but not in this section, and it produces an error, a runtime error will be raised. - -Provide a list of attributes instead of a single attribute to add the message to multiple attributes. - -By adding the `check` option, the migration generator will include it when generating migrations. - - - - -### Examples -``` -check_constraint :price, "price_must_be_positive", check: "price > 0", message: "price must be positive" - -``` - - - -### Arguments - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - attribute - - - * - - - `any` - - - - The attribute or list of attributes to which an error will be added if the check constraint fails -
- - - name - - - * - - - String.t - - - - The name of the constraint -
-### Options - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDocs
- - - message - - - - - String.t - - - - The message to be added if the check constraint fails -
- - - check - - - - - String.t - - - - The contents of the check. If this is set, the migration generator will include it when generating migrations -
- - - - - -### Introspection - -Target: `AshPostgres.CheckConstraint` - - - - - - diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md new file mode 100644 index 00000000..2262d16c --- /dev/null +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -0,0 +1,387 @@ + +# DSL: AshPostgres.DataLayer + +A postgres data layer that leverages Ecto's postgres capabilities. + + +## postgres +Postgres data layer configuration + + +### Nested DSLs + * [custom_indexes](#postgres-custom_indexes) + * index + * [custom_statements](#postgres-custom_statements) + * statement + * [manage_tenant](#postgres-manage_tenant) + * [references](#postgres-references) + * reference + * [check_constraints](#postgres-check_constraints) + * check_constraint + + +### Examples +``` +postgres do + repo MyApp.Repo + table "organizations" +end + +``` + + + + +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`repo`](#postgres-repo){: #postgres-repo .spark-required} | `module \| (any, any -> any)` | | The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more. Can also be a function that takes a resource and a type `:read \| :mutate` and returns the repo | +| [`migrate?`](#postgres-migrate?){: #postgres-migrate? } | `boolean` | `true` | Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` | +| [`migration_types`](#postgres-migration_types){: #postgres-migration_types } | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | +| [`migration_defaults`](#postgres-migration_defaults){: #postgres-migration_defaults } | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | +| [`base_filter_sql`](#postgres-base_filter_sql){: #postgres-base_filter_sql } | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | +| [`simple_join_first_aggregates`](#postgres-simple_join_first_aggregates){: #postgres-simple_join_first_aggregates } | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | +| [`skip_unique_indexes`](#postgres-skip_unique_indexes){: #postgres-skip_unique_indexes } | `atom \| list(atom)` | `false` | Skip generating unique indexes when generating migrations | +| [`unique_index_names`](#postgres-unique_index_names){: #postgres-unique_index_names } | `list({list(atom), String.t} \| {list(atom), String.t, String.t})` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | +| [`exclusion_constraint_names`](#postgres-exclusion_constraint_names){: #postgres-exclusion_constraint_names } | ``any`` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | +| [`identity_index_names`](#postgres-identity_index_names){: #postgres-identity_index_names } | ``any`` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | +| [`foreign_key_names`](#postgres-foreign_key_names){: #postgres-foreign_key_names } | `list({atom \| String.t, String.t} \| {atom \| String.t, String.t, String.t})` | `[]` | A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` | +| [`migration_ignore_attributes`](#postgres-migration_ignore_attributes){: #postgres-migration_ignore_attributes } | `list(atom)` | `[]` | A list of attributes that will be ignored when generating migrations. | +| [`table`](#postgres-table){: #postgres-table } | `String.t` | | The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. | +| [`schema`](#postgres-schema){: #postgres-schema } | `String.t` | | The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. | +| [`polymorphic?`](#postgres-polymorphic?){: #postgres-polymorphic? } | `boolean` | `false` | Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. | + + +## postgres.custom_indexes +A section for configuring indexes to be created by the migration generator. + +In general, prefer to use `identities` for simple unique constraints. This is a tool to allow +for declaring more complex indexes. + + +### Nested DSLs + * [index](#postgres-custom_indexes-index) + + +### Examples +``` +custom_indexes do + index [:column1, :column2], unique: true, where: "thing = TRUE" +end + +``` + + + + +## postgres.custom_indexes.index +```elixir +index fields +``` + + +Add an index to be managed by the migration generator. + + + + +### Examples +``` +index ["column", "column2"], unique: true, where: "thing = TRUE" +``` + + + +### Arguments + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`fields`](#postgres-custom_indexes-index-fields){: #postgres-custom_indexes-index-fields } | `atom \| String.t \| list(atom \| String.t)` | | The fields to include in the index. | +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`name`](#postgres-custom_indexes-index-name){: #postgres-custom_indexes-index-name } | `String.t` | | the name of the index. Defaults to "#{table}_#{column}_index". | +| [`unique`](#postgres-custom_indexes-index-unique){: #postgres-custom_indexes-index-unique } | `boolean` | `false` | indicates whether the index should be unique. | +| [`concurrently`](#postgres-custom_indexes-index-concurrently){: #postgres-custom_indexes-index-concurrently } | `boolean` | `false` | indicates whether the index should be created/dropped concurrently. | +| [`using`](#postgres-custom_indexes-index-using){: #postgres-custom_indexes-index-using } | `String.t` | | configures the index type. | +| [`prefix`](#postgres-custom_indexes-index-prefix){: #postgres-custom_indexes-index-prefix } | `String.t` | | specify an optional prefix for the index. | +| [`where`](#postgres-custom_indexes-index-where){: #postgres-custom_indexes-index-where } | `String.t` | | specify conditions for a partial index. | +| [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | +| [`include`](#postgres-custom_indexes-index-include){: #postgres-custom_indexes-index-include } | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | + + + + + +### Introspection + +Target: `AshPostgres.CustomIndex` + + +## postgres.custom_statements +A section for configuring custom statements to be added to migrations. + +Changing custom statements may require manual intervention, because Ash can't determine what order they should run +in (i.e if they depend on table structure that you've added, or vice versa). As such, any `down` statements we run +for custom statements happen first, and any `up` statements happen last. + +Additionally, when changing a custom statement, we must make some assumptions, i.e that we should migrate +the old structure down using the previously configured `down` and recreate it. + +This may not be desired, and so what you may end up doing is simply modifying the old migration and deleting whatever was +generated by the migration generator. As always: read your migrations after generating them! + + +### Nested DSLs + * [statement](#postgres-custom_statements-statement) + + +### Examples +``` +custom_statements do + # the name is used to detect if you remove or modify the statement + statement :pgweb_idx do + up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));" + down "DROP INDEX pgweb_idx;" + end +end + +``` + + + + +## postgres.custom_statements.statement +```elixir +statement name +``` + + +Add a custom statement for migrations. + + + + +### Examples +``` +statement :pgweb_idx do + up "CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector('english', title || ' ' || body));" + down "DROP INDEX pgweb_idx;" +end + +``` + + + +### Arguments + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`name`](#postgres-custom_statements-statement-name){: #postgres-custom_statements-statement-name .spark-required} | `atom` | | The name of the statement, must be unique within the resource | +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`up`](#postgres-custom_statements-statement-up){: #postgres-custom_statements-statement-up .spark-required} | `String.t` | | How to create the structure of the statement | +| [`down`](#postgres-custom_statements-statement-down){: #postgres-custom_statements-statement-down .spark-required} | `String.t` | | How to tear down the structure of the statement | +| [`code?`](#postgres-custom_statements-statement-code?){: #postgres-custom_statements-statement-code? } | `boolean` | `false` | By default, we place the strings inside of ecto migration's `execute/1` function and assume they are sql. Use this option if you want to provide custom elixir code to be placed directly in the migrations | + + + + + +### Introspection + +Target: `AshPostgres.Statement` + + +## postgres.manage_tenant +Configuration for the behavior of a resource that manages a tenant + + + + +### Examples +``` +manage_tenant do + template ["organization_", :id] + create? true + update? false +end + +``` + + + + +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`template`](#postgres-manage_tenant-template){: #postgres-manage_tenant-template .spark-required} | `String.t \| atom \| list(String.t \| atom)` | | A template that will cause the resource to create/manage the specified schema. | +| [`create?`](#postgres-manage_tenant-create?){: #postgres-manage_tenant-create? } | `boolean` | `true` | Whether or not to automatically create a tenant when a record is created | +| [`update?`](#postgres-manage_tenant-update?){: #postgres-manage_tenant-update? } | `boolean` | `true` | Whether or not to automatically update the tenant name if the record is udpated | + + + + +## postgres.references +A section for configuring the references (foreign keys) in resource migrations. + +This section is only relevant if you are using the migration generator with this resource. +Otherwise, it has no effect. + + +### Nested DSLs + * [reference](#postgres-references-reference) + + +### Examples +``` +references do + reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" +end + +``` + + + + +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`polymorphic_on_delete`](#postgres-references-polymorphic_on_delete){: #postgres-references-polymorphic_on_delete } | `:delete \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | +| [`polymorphic_on_update`](#postgres-references-polymorphic_on_update){: #postgres-references-polymorphic_on_update } | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | +| [`polymorphic_name`](#postgres-references-polymorphic_name){: #postgres-references-polymorphic_name } | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | + + + +## postgres.references.reference +```elixir +reference relationship +``` + + +Configures the reference for a relationship in resource migrations. + +Keep in mind that multiple relationships can theoretically involve the same destination and foreign keys. +In those cases, you only need to configure the `reference` behavior for one of them. Any conflicts will result +in an error, across this resource and any other resources that share a table with this one. For this reason, +instead of adding a reference configuration for `:nothing`, its best to just leave the configuration out, as that +is the default behavior if *no* relationship anywhere has configured the behavior of that reference. + + + + +### Examples +``` +reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" +``` + + + +### Arguments + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`relationship`](#postgres-references-reference-relationship){: #postgres-references-reference-relationship .spark-required} | `atom` | | The relationship to be configured | +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`ignore?`](#postgres-references-reference-ignore?){: #postgres-references-reference-ignore? } | `boolean` | | If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way | +| [`on_delete`](#postgres-references-reference-on_delete){: #postgres-references-reference-on_delete } | `:delete \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | +| [`on_update`](#postgres-references-reference-on_update){: #postgres-references-reference-on_update } | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | +| [`deferrable`](#postgres-references-reference-deferrable){: #postgres-references-reference-deferrable } | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | +| [`name`](#postgres-references-reference-name){: #postgres-references-reference-name } | `String.t` | | The name of the foreign key to generate in the database. Defaults to __fkey | +| [`match_with`](#postgres-references-reference-match_with){: #postgres-references-reference-match_with } | `Keyword.t` | | Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute. | +| [`match_type`](#postgres-references-reference-match_type){: #postgres-references-reference-match_type } | `:simple \| :partial \| :full` | | select if the match is `:simple`, `:partial`, or `:full` | + + + + + +### Introspection + +Target: `AshPostgres.Reference` + + +## postgres.check_constraints +A section for configuring the check constraints for a given table. + +This can be used to automatically create those check constraints, or just to provide message when they are raised + + +### Nested DSLs + * [check_constraint](#postgres-check_constraints-check_constraint) + + +### Examples +``` +check_constraints do + check_constraint :price, "price_must_be_positive", check: "price > 0", message: "price must be positive" +end + +``` + + + + +## postgres.check_constraints.check_constraint +```elixir +check_constraint attribute, name +``` + + +Add a check constraint to be validated. + +If a check constraint exists on the table but not in this section, and it produces an error, a runtime error will be raised. + +Provide a list of attributes instead of a single attribute to add the message to multiple attributes. + +By adding the `check` option, the migration generator will include it when generating migrations. + + + + +### Examples +``` +check_constraint :price, "price_must_be_positive", check: "price > 0", message: "price must be positive" + +``` + + + +### Arguments + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`attribute`](#postgres-check_constraints-check_constraint-attribute){: #postgres-check_constraints-check_constraint-attribute .spark-required} | ``any`` | | The attribute or list of attributes to which an error will be added if the check constraint fails | +| [`name`](#postgres-check_constraints-check_constraint-name){: #postgres-check_constraints-check_constraint-name .spark-required} | `String.t` | | The name of the constraint | +### Options + +| Name | Type | Default | Docs | +|------|------|---------|------| +| [`message`](#postgres-check_constraints-check_constraint-message){: #postgres-check_constraints-check_constraint-message } | `String.t` | | The message to be added if the check constraint fails | +| [`check`](#postgres-check_constraints-check_constraint-check){: #postgres-check_constraints-check_constraint-check } | `String.t` | | The contents of the check. If this is set, the migration generator will include it when generating migrations | + + + + + +### Introspection + +Target: `AshPostgres.CheckConstraint` + + + + + + + + diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 50e9d23f..cf06b86e 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1365,7 +1365,7 @@ defmodule AshPostgres.DataLayer do [], fragment( "COALESCE(EXCLUDED.?, ?)", - literal(^to_string(upsert_field)), + literal(^to_string(get_source_for_upsert_field(upsert_field, resource))), ^default ) )} @@ -1377,12 +1377,25 @@ defmodule AshPostgres.DataLayer do {upsert_field, Ecto.Query.dynamic( [], - fragment("EXCLUDED.?", literal(^to_string(upsert_field))) + fragment( + "EXCLUDED.?", + literal(^to_string(get_source_for_upsert_field(upsert_field, resource))) + ) )} end end) end + defp get_source_for_upsert_field(field, resource) do + case Ash.Resource.Info.attribute(resource, field) do + %{source: source} when not is_nil(source) -> + source + + _ -> + field + end + end + @impl true def create(resource, changeset) do changeset = %{ diff --git a/priv/resource_snapshots/test_repo/posts/20231219132807.json b/priv/resource_snapshots/test_repo/posts/20231219132807.json new file mode 100644 index 00000000..4bf3be16 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20231219132807.json @@ -0,0 +1,303 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "528A5F10049859ED0555DBAB43B0BA1D87A36F1EDB06FF9D9108F8E27F054898", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "unique": true, + "concurrently": true, + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20231219132807_migrate_resources13.exs b/priv/test_repo/migrations/20231219132807_migrate_resources13.exs new file mode 100644 index 00000000..088d2ae3 --- /dev/null +++ b/priv/test_repo/migrations/20231219132807_migrate_resources13.exs @@ -0,0 +1,17 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources13 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 + rename table(:posts), :title, to: :title_column + end + + def down do + rename table(:posts), :title_column, to: :title + end +end \ No newline at end of file diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c1c1127e..c96f5778 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -74,7 +74,11 @@ defmodule AshPostgres.Test.Post do attributes do uuid_primary_key(:id, writable?: true) - attribute(:title, :string) + + attribute(:title, :string) do + source(:title_column) + end + attribute(:score, :integer) attribute(:public, :boolean) attribute(:category, :ci_string) From 61d229c6852135d4ff86df505d1efe66978dff88 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 19 Dec 2023 18:14:12 -0500 Subject: [PATCH 0130/1215] fix: use lateral joins when joining to subquery w/ parent reference --- lib/aggregate.ex | 146 ++++++++---------- lib/calculation.ex | 96 ++++++------ lib/data_layer.ex | 4 +- lib/join.ex | 60 +++++-- mix.exs | 2 +- mix.lock | 2 +- test/complex_calculations_test.exs | 15 +- .../complex_calculations/resources/channel.ex | 9 ++ .../resources/dm_channel.ex | 2 + 9 files changed, 197 insertions(+), 139 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 55824106..accb119c 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -365,7 +365,7 @@ defmodule AshPostgres.Aggregate do relationship_path, aggregates, is_single?, - source_binding + _source_binding ) do Enum.reduce_while(aggregates, {:ok, agg_query}, fn aggregate, {:ok, agg_query} -> filter = @@ -379,86 +379,47 @@ defmodule AshPostgres.Aggregate do aggregate.query.filter end - used_calculations = - Ash.Filter.used_calculations( - filter, - first_relationship.destination, - relationship_path - ) - related = Ash.Resource.Info.related(first_relationship.destination, relationship_path) - used_calculations = + agg_query = case Ash.Resource.Info.calculation(related, aggregate.field) do %{name: name, calculation: {module, opts}, type: type, constraints: constraints} -> {:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, {type, constraints}) + expression = module.expression(opts, aggregate.context) + + expression = + Ash.Filter.build_filter_from_template( + expression, + aggregate.context[:actor], + aggregate.context, + aggregate.context + ) - if new_calc in used_calculations do - used_calculations - else - [ - new_calc - | used_calculations - ] - end + {:ok, expression} = + Ash.Filter.hydrate_refs(expression, %{ + resource: related, + public?: false + }) + + {:ok, agg_query} = + AshPostgres.DataLayer.add_calculations( + agg_query, + [{new_calc, expression}], + agg_query.__ash_bindings__.resource, + false + ) + + agg_query nil -> - used_calculations + agg_query end - exprs = - Enum.map(used_calculations, fn calculation -> - case Ash.Filter.hydrate_refs( - calculation.module.expression(calculation.opts, calculation.context), - %{ - resource: aggregate.query.resource, - aggregates: %{}, - calculations: %{}, - public?: false - } - ) do - {:ok, hydrated} -> - hydrated - - {:error, _error} -> - raise """ - Failed to hydrate references for resource #{inspect(aggregate.query.resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))} - """ - end - end) - - {:ok, agg_query} = - AshPostgres.Join.join_all_relationships( - agg_query, - exprs, - [] - ) - - used_aggregates = - used_aggregates( - filter, - first_relationship.destination, - used_calculations, - relationship_path - ) - - case add_aggregates( - agg_query, - used_aggregates, - first_relationship.destination, - false, - source_binding - ) do - {:ok, agg_query} -> - if has_filter?(aggregate.query) && is_single? do - {:cont, - AshPostgres.DataLayer.filter(agg_query, filter, agg_query.__ash_bindings__.resource)} - else - {:cont, {:ok, agg_query}} - end - - other -> - {:halt, other} + if has_filter?(aggregate.query) && is_single? do + {:cont, + AshPostgres.DataLayer.filter(agg_query, filter, agg_query.__ash_bindings__.resource)} + else + {:cont, {:ok, agg_query}} end end) end @@ -932,7 +893,7 @@ defmodule AshPostgres.Aggregate do ) end - filtered = filter_field(sorted, query, aggregate, relationship_path, is_single?) + {query, filtered} = filter_field(sorted, query, aggregate, relationship_path, is_single?) value = Ecto.Query.dynamic(fragment("(?)[1]", ^filtered)) @@ -1037,7 +998,7 @@ defmodule AshPostgres.Aggregate do end end - filtered = filter_field(sorted, query, aggregate, relationship_path, is_single?) + {query, filtered} = filter_field(sorted, query, aggregate, relationship_path, is_single?) with_default = if aggregate.default_value do @@ -1122,7 +1083,7 @@ defmodule AshPostgres.Aggregate do module.dynamic(opts, binding) end - filtered = filter_field(field, query, aggregate, relationship_path, is_single?) + {query, filtered} = filter_field(field, query, aggregate, relationship_path, is_single?) with_default = if aggregate.default_value do @@ -1145,8 +1106,8 @@ defmodule AshPostgres.Aggregate do select_or_merge(query, aggregate.name, cast) end - defp filter_field(field, _query, _aggregate, _relationship_path, true) do - field + defp filter_field(field, query, _aggregate, _relationship_path, true) do + {query, field} end defp filter_field(field, query, aggregate, relationship_path, _is_single?) do @@ -1157,6 +1118,35 @@ defmodule AshPostgres.Aggregate do relationship_path ) + used_calculations = + Ash.Filter.used_calculations( + filter, + query.__ash_bindings__.resource + ) + + used_aggregates = + filter + |> AshPostgres.Aggregate.used_aggregates( + query.__ash_bindings__.resource, + used_calculations, + [] + ) + |> Enum.map(fn aggregate -> + %{aggregate | load: aggregate.name} + end) + + {:ok, query} = + AshPostgres.Join.join_all_relationships(query, filter) + + {:ok, query} = + AshPostgres.Aggregate.add_aggregates( + query, + used_aggregates, + query.__ash_bindings__.resource, + false, + 0 + ) + expr = AshPostgres.Expr.dynamic_expr( query, @@ -1166,9 +1156,9 @@ defmodule AshPostgres.Aggregate do AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) ) - Ecto.Query.dynamic(filter(^field, ^expr)) + {query, Ecto.Query.dynamic(filter(^field, ^expr))} else - field + {query, field} end end diff --git a/lib/calculation.ex b/lib/calculation.ex index d3d44a2a..3249337d 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -3,9 +3,9 @@ defmodule AshPostgres.Calculation do require Ecto.Query - def add_calculations(query, [], _, _), do: {:ok, query} + def add_calculations(query, [], _, _, _select?), do: {:ok, query} - def add_calculations(query, calculations, resource, source_binding) do + def add_calculations(query, calculations, resource, source_binding, select?) do query = AshPostgres.DataLayer.default_bindings(query, resource) {:ok, query} = @@ -46,50 +46,54 @@ defmodule AshPostgres.Calculation do source_binding ) do {:ok, query} -> - query = - if query.select do - query - else - Ecto.Query.select_merge(query, %{}) - end - - dynamics = - Enum.map(calculations, fn {calculation, expression} -> - type = - AshPostgres.Types.parameterized_type( - calculation.type, - Map.get(calculation, :constraints, []) - ) - - expression = - Ash.Actions.Read.add_calc_context_to_filter( - expression, - calculation.context[:actor], - calculation.context[:authorize?], - calculation.context[:tenant], - calculation.context[:tracer] - ) - - expr = - AshPostgres.Expr.dynamic_expr( - query, - expression, - query.__ash_bindings__, - false, - type - ) - - expr = - if type do - Ecto.Query.dynamic(type(^expr, ^type)) - else - expr - end - - {calculation.load, calculation.name, expr} - end) - - {:ok, add_calculation_selects(query, dynamics)} + if select? do + query = + if query.select do + query + else + Ecto.Query.select_merge(query, %{}) + end + + dynamics = + Enum.map(calculations, fn {calculation, expression} -> + type = + AshPostgres.Types.parameterized_type( + calculation.type, + Map.get(calculation, :constraints, []) + ) + + expression = + Ash.Actions.Read.add_calc_context_to_filter( + expression, + calculation.context[:actor], + calculation.context[:authorize?], + calculation.context[:tenant], + calculation.context[:tracer] + ) + + expr = + AshPostgres.Expr.dynamic_expr( + query, + expression, + query.__ash_bindings__, + false, + type + ) + + expr = + if type do + Ecto.Query.dynamic(type(^expr, ^type)) + else + expr + end + + {calculation.load, calculation.name, expr} + end) + + {:ok, add_calculation_selects(query, dynamics)} + else + {:ok, query} + end {:error, error} -> {:error, error} diff --git a/lib/data_layer.ex b/lib/data_layer.ex index cf06b86e..b0079f22 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2627,8 +2627,8 @@ defmodule AshPostgres.DataLayer do end @impl true - def add_calculations(query, calculations, resource) do - AshPostgres.Calculation.add_calculations(query, calculations, resource, 0) + def add_calculations(query, calculations, resource, select? \\ true) do + AshPostgres.Calculation.add_calculations(query, calculations, resource, 0, select?) end @doc false diff --git a/lib/join.ex b/lib/join.ex index 561bb3db..a03d4bc2 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -894,8 +894,10 @@ defmodule AshPostgres.Join do use_root_query_bindings? = Enum.empty?(used_aggregates) + needs_subquery? = Map.get(relationship, :from_many?, false) + root_bindings = - if use_root_query_bindings? do + if use_root_query_bindings? && !needs_subquery? do query.__ash_bindings__ end @@ -905,7 +907,9 @@ defmodule AshPostgres.Join do query, sort?, full_path, - root_bindings + root_bindings, + nil, + use_root_query_bindings? && !needs_subquery? ) do {:error, error} -> {:error, error} @@ -916,8 +920,6 @@ defmodule AshPostgres.Join do |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) - needs_subquery? = Map.get(relationship, :from_many?) - relationship_destination = if needs_subquery? do subquery(from(row in relationship_destination, limit: 1)) @@ -961,22 +963,22 @@ defmodule AshPostgres.Join do end query = - case {kind, Map.get(relationship, :no_attributes?)} do - {:inner, true} -> + case {kind, Map.get(relationship, :no_attributes?, false), needs_subquery?} do + {:inner, true, false} -> from([{row, current_binding}] in query, join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) - {_, true} -> + {:inner, true, true} -> from([{row, current_binding}] in query, - left_join: destination in ^relationship_destination, + inner_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) - {:inner, _} -> + {:inner, false, false} -> from([{row, current_binding}] in query, join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, @@ -988,10 +990,48 @@ defmodule AshPostgres.Join do ) ) - _ -> + {:inner, false, true} -> + from([{row, current_binding}] in query, + inner_lateral_join: destination in ^relationship_destination, + as: ^initial_ash_bindings.current, + on: + field(row, ^relationship.source_attribute) == + field( + destination, + ^relationship.destination_attribute + ) + ) + + {:left, true, false} -> from([{row, current_binding}] in query, left_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, + on: true + ) + + {:left, true, true} -> + from([{row, current_binding}] in query, + left_lateral_join: destination in ^relationship_destination, + as: ^initial_ash_bindings.current, + on: true + ) + + {:left, false, false} -> + from([{row, current_binding}] in query, + left_join: destination in ^relationship_destination, + as: ^initial_ash_bindings.current, + on: + field(row, ^relationship.source_attribute) == + field( + destination, + ^relationship.destination_attribute + ) + ) + + {:left, false, true} -> + from([{row, current_binding}] in query, + left_lateral_join: destination in ^relationship_destination, + as: ^initial_ash_bindings.current, on: field(row, ^relationship.source_attribute) == field( diff --git a/mix.exs b/mix.exs index 85a1e098..d9ad9b67 100644 --- a/mix.exs +++ b/mix.exs @@ -38,7 +38,7 @@ defmodule AshPostgres.MixProject do package: package(), source_url: "https://github.com/ash-project/ash_postgres/", homepage_url: "https://ash-hq.org/", - consolidate_protocols: true + consolidate_protocols: Mix.env() == :prod ] end diff --git a/mix.lock b/mix.lock index a36a4d44..451389b0 100644 --- a/mix.lock +++ b/mix.lock @@ -37,7 +37,7 @@ "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, - "spark": {:hex, :spark, "1.1.52", "e0ddd137899c11fb44ef46cda346a112e60365b93e50264da976f45b1c6e28c5", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "2d8b354103eb4ae5fb4ed5f885d491e3ed5684ccb57806c3980fcc15a4b597d6"}, + "spark": {:hex, :spark, "1.1.53", "db8a374ef6ada4f38389386bec76b2fa6331d4755308a6e359acad16472e29ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "5f8a8e2b4abd2544517bb8d29c28576239254b5979d66d9781b154706c4199dd"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index ac1c75cb..a82f393d 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -1,6 +1,8 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do use AshPostgres.RepoCase, async: false + require Ash.Query + test "complex calculation" do certification = AshPostgres.Test.ComplexCalculations.Certification @@ -178,8 +180,19 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do channel = channel - |> AshPostgres.Test.ComplexCalculations.Api.load!(:dm_name, actor: user_2) + |> AshPostgres.Test.ComplexCalculations.Api.load!([:dm_name, :foo], actor: user_2) assert channel.dm_name == user_2.name end + + test "calculations with parent filters can be filtered on themselves" do + AshPostgres.Test.ComplexCalculations.DMChannel + |> Ash.Changeset.new() + |> AshPostgres.Test.ComplexCalculations.Api.create!() + + assert [%{foo: "foobar"}] = + AshPostgres.Test.ComplexCalculations.Channel + |> Ash.Query.filter(foo == "foobar") + |> AshPostgres.Test.ComplexCalculations.Api.read!(load: :foo) + end end diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex index 188898e7..6d9d3a85 100644 --- a/test/support/complex_calculations/resources/channel.ex +++ b/test/support/complex_calculations/resources/channel.ex @@ -41,6 +41,13 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do api(AshPostgres.Test.ComplexCalculations.Api) destination_attribute(:id) end + + has_one :dm_channel_with_same_id, AshPostgres.Test.ComplexCalculations.DMChannel do + no_attributes?(true) + from_many?(true) + filter(expr(parent(id) == id)) + api(AshPostgres.Test.ComplexCalculations.Api) + end end aggregates do @@ -68,6 +75,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do end calculate(:dm_name, :string, expr(dm_channel_name)) + + calculate(:foo, :string, expr(dm_channel_with_same_id.foobar)) end policies do diff --git a/test/support/complex_calculations/resources/dm_channel.ex b/test/support/complex_calculations/resources/dm_channel.ex index a5d71004..b8b74ad1 100644 --- a/test/support/complex_calculations/resources/dm_channel.ex +++ b/test/support/complex_calculations/resources/dm_channel.ex @@ -46,6 +46,8 @@ defmodule AshPostgres.Test.ComplexCalculations.DMChannel do end calculations do + calculate(:foobar, :string, expr("foobar")) + calculate :name, :string do calculation( expr( From 8192d1eb3b0af5d4209a828afb789bbe024db413 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 19 Dec 2023 22:00:40 -0500 Subject: [PATCH 0131/1215] improvement: clean up nested if statements to single case statements --- lib/expr.ex | 118 ++++++++++++++++++++++++++++++++++++++++++++-------- mix.lock | 2 +- 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index a9a152de..f4fd42b0 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -344,28 +344,37 @@ defmodule AshPostgres.Expr do when_true = do_dynamic_expr(query, when_true, bindings, pred_embedded? || embedded?, when_true_type) - when_false = - do_dynamic_expr( - query, - when_false, - bindings, - pred_embedded? || embedded?, - when_false_type - ) + {additional_cases, when_false} = + extract_cases(query, when_false, bindings, pred_embedded? || embedded?, when_false_type) + + additional_case_fragments = + additional_cases + |> Enum.flat_map(fn {condition, when_true} -> + [ + raw: " WHEN ", + casted_expr: condition, + raw: " THEN ", + casted_expr: when_true + ] + end) do_dynamic_expr( query, %Fragment{ embedded?: pred_embedded?, - arguments: [ - raw: "(CASE WHEN ", - casted_expr: condition, - raw: " THEN ", - casted_expr: when_true, - raw: " ELSE ", - casted_expr: when_false, - raw: " END)" - ] + arguments: + [ + raw: "(CASE WHEN ", + casted_expr: condition, + raw: " THEN ", + casted_expr: when_true + ] ++ + additional_case_fragments ++ + [ + raw: " ELSE ", + casted_expr: when_false, + raw: " END)" + ] }, bindings, embedded?, @@ -1376,6 +1385,81 @@ defmodule AshPostgres.Expr do end end + defp extract_cases( + query, + expr, + bindings, + embedded?, + type, + acc \\ [] + ) + + defp extract_cases( + query, + %If{arguments: [condition, when_true, when_false], embedded?: pred_embedded?}, + bindings, + embedded?, + type, + acc + ) do + [condition_type, when_true_type, when_false_type] = + case AshPostgres.Types.determine_types(If, [condition, when_true, when_false]) do + [condition_type, when_true] -> + [condition_type, when_true, nil] + + [condition_type, when_true, when_false] -> + [condition_type, when_true, when_false] + end + |> case do + [condition_type, nil, nil] -> + [condition_type, type, type] + + [condition_type, when_true, nil] -> + [condition_type, when_true, type] + + [condition_type, nil, when_false] -> + [condition_type, type, when_false] + + [condition_type, when_true, when_false] -> + [condition_type, when_true, when_false] + end + + condition = + do_dynamic_expr(query, condition, bindings, pred_embedded? || embedded?, condition_type) + + when_true = + do_dynamic_expr(query, when_true, bindings, pred_embedded? || embedded?, when_true_type) + + extract_cases( + query, + when_false, + bindings, + embedded?, + when_false_type, + [{condition, when_true} | acc] + ) + end + + defp extract_cases( + query, + other, + bindings, + embedded?, + type, + acc + ) do + expr = + do_dynamic_expr( + query, + other, + bindings, + embedded?, + type + ) + + {Enum.reverse(acc), expr} + end + defp split_at_paths(type, constraints, next, acc \\ [{:bracket, [], nil, nil}]) defp split_at_paths(_type, _constraints, [], acc) do diff --git a/mix.lock b/mix.lock index 451389b0..9e723186 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.13", "a3bb846238d4eb029da00583554f074d73950cfa4bc2f5964283d2e4491a9543", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b41a3e029b1553e71a0cab7db66d98a1ea7a883d301b866630ef9d09e64d38ee"}, + "ash": {:hex, :ash, "2.17.14", "ccb68a0eacd7d4f4652a01baa3ceda510d793ab769c82958d644638905f08f7d", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b09a585924f222a1d353ebd13ec8691c4b9c5e37a8be271ee8960c34feb3fad0"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From 9ca92724268c385a66862da947bc34625542ab6a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 21 Dec 2023 16:56:57 -0500 Subject: [PATCH 0132/1215] improvement: only start savepoints when necessary --- lib/aggregate.ex | 104 ++++--- lib/calculation.ex | 9 +- lib/data_layer.ex | 120 ++++---- lib/expr.ex | 631 +++++++++++++++++++++++++-------------- lib/join.ex | 49 +-- lib/sort.ex | 18 +- test/error_expr_test.exs | 19 ++ 7 files changed, 606 insertions(+), 344 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index accb119c..4afe5fb2 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -101,14 +101,16 @@ defmodule AshPostgres.Aggregate do Map.get(aggregate.query, :filter) end - exists = + {exists, acc} = AshPostgres.Expr.dynamic_expr( query, %Ash.Query.Exists{path: aggregate.relationship_path, expr: expr}, query.__ash_bindings__ ) - {:cont, {:ok, query, [{aggregate.load, aggregate.name, exists} | dynamics]}} + {:cont, + {:ok, AshPostgres.DataLayer.merge_expr_accumulator(query, acc), + [{aggregate.load, aggregate.name, exists} | dynamics]}} true -> root_data_path = @@ -120,7 +122,7 @@ defmodule AshPostgres.Aggregate do [] end - with {:ok, agg_root_query} <- + with {:ok, agg_root_query, acc} <- AshPostgres.Join.maybe_get_resource_query( first_relationship.destination, first_relationship, @@ -178,6 +180,8 @@ defmodule AshPostgres.Aggregate do source_binding, root_data_path ) do + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + if select? do new_dynamics = Enum.map( @@ -320,7 +324,7 @@ defmodule AshPostgres.Aggregate do resource: resource } - value = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + {value, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) @@ -342,7 +346,7 @@ defmodule AshPostgres.Aggregate do with_default end - {:ok, query, casted} + {:ok, AshPostgres.DataLayer.merge_expr_accumulator(query, acc), casted} {:error, error} -> {:error, error} @@ -487,7 +491,7 @@ defmodule AshPostgres.Aggregate do ) do join_relationship_struct = Ash.Resource.Info.relationship(source, join_relationship) - {:ok, through} = + {:ok, through, acc} = AshPostgres.Join.maybe_get_resource_query( join_relationship_struct.destination, join_relationship_struct, @@ -532,14 +536,13 @@ defmodule AshPostgres.Aggregate do on: true ) - AshPostgres.DataLayer.add_binding( - query, - %{ - path: root_data_path, - type: :aggregate, - aggregates: aggregates - } - ) + query + |> AshPostgres.DataLayer.add_binding(%{ + path: root_data_path, + type: :aggregate, + aggregates: aggregates + }) + |> AshPostgres.DataLayer.merge_expr_accumulator(acc) end defp join_subquery( @@ -852,11 +855,11 @@ defmodule AshPostgres.Aggregate do [:left, :inner, :root] ) - field = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + {field, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) has_sort? = has_sort?(aggregate.query) - sorted = + {sorted, query} = if has_sort? || first_relationship.sort not in [nil, []] do {sort, binding} = if has_sort? do @@ -885,12 +888,18 @@ defmodule AshPostgres.Aggregate do ["array_agg(? ORDER BY #{question_marks})", field] ++ sort_expr ) - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) + {sort_expr, acc} = + AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) + + query = + AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + + {sort_expr, query} else - Ecto.Query.dynamic( - [row], - fragment("array_agg(?)", ^field) - ) + {Ecto.Query.dynamic( + [row], + fragment("array_agg(?)", ^field) + ), query} end {query, filtered} = filter_field(sorted, query, aggregate, relationship_path, is_single?) @@ -915,7 +924,11 @@ defmodule AshPostgres.Aggregate do with_default end - select_or_merge(query, aggregate.name, casted) + select_or_merge( + AshPostgres.DataLayer.merge_expr_accumulator(query, acc), + aggregate.name, + casted + ) end def add_subquery_aggregate_select( @@ -943,11 +956,11 @@ defmodule AshPostgres.Aggregate do resource: query.__ash_bindings__.resource } - field = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + {field, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) has_sort? = has_sort?(aggregate.query) - sorted = + {sorted, query} = if has_sort? || first_relationship.sort not in [nil, []] do {sort, binding} = if has_sort? do @@ -983,18 +996,24 @@ defmodule AshPostgres.Aggregate do ["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr ) - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) + {expr, acc} = + AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) + + query = + AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + + {expr, query} else if Map.get(aggregate, :uniq?) do - Ecto.Query.dynamic( - [row], - fragment("array_agg(DISTINCT ?)", ^field) - ) + {Ecto.Query.dynamic( + [row], + fragment("array_agg(DISTINCT ?)", ^field) + ), query} else - Ecto.Query.dynamic( - [row], - fragment("array_agg(?)", ^field) - ) + {Ecto.Query.dynamic( + [row], + fragment("array_agg(?)", ^field) + ), query} end end @@ -1018,7 +1037,11 @@ defmodule AshPostgres.Aggregate do with_default end - select_or_merge(query, aggregate.name, cast) + select_or_merge( + AshPostgres.DataLayer.merge_expr_accumulator(query, acc), + aggregate.name, + cast + ) end def add_subquery_aggregate_select( @@ -1038,12 +1061,14 @@ defmodule AshPostgres.Aggregate do resource: resource } - field = + {field, query} = if kind == :custom do # we won't use this if its custom so don't try to make one - nil + {nil, query} else - AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + {expr, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) + + {expr, AshPostgres.DataLayer.merge_expr_accumulator(query, acc)} end type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) @@ -1147,7 +1172,7 @@ defmodule AshPostgres.Aggregate do 0 ) - expr = + {expr, acc} = AshPostgres.Expr.dynamic_expr( query, filter, @@ -1156,7 +1181,8 @@ defmodule AshPostgres.Aggregate do AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) ) - {query, Ecto.Query.dynamic(filter(^field, ^expr))} + {AshPostgres.DataLayer.merge_expr_accumulator(query, acc), + Ecto.Query.dynamic(filter(^field, ^expr))} else {query, field} end diff --git a/lib/calculation.ex b/lib/calculation.ex index 3249337d..21d83286 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -54,8 +54,8 @@ defmodule AshPostgres.Calculation do Ecto.Query.select_merge(query, %{}) end - dynamics = - Enum.map(calculations, fn {calculation, expression} -> + {dynamics, query} = + Enum.reduce(calculations, {[], query}, fn {calculation, expression}, {list, query} -> type = AshPostgres.Types.parameterized_type( calculation.type, @@ -71,7 +71,7 @@ defmodule AshPostgres.Calculation do calculation.context[:tracer] ) - expr = + {expr, acc} = AshPostgres.Expr.dynamic_expr( query, expression, @@ -87,7 +87,8 @@ defmodule AshPostgres.Calculation do expr end - {calculation.load, calculation.name, expr} + {[{calculation.load, calculation.name, expr} | list], + AshPostgres.DataLayer.merge_expr_accumulator(query, acc)} end) {:ok, add_calculation_selects(query, dynamics)} diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b0079f22..8ff0c54e 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -648,7 +648,7 @@ defmodule AshPostgres.DataLayer do else repo = dynamic_repo(resource, query) - with_savepoint(repo, fn -> + with_savepoint(repo, query, fn -> {:ok, repo.all(query, repo_opts(nil, nil, resource))} end) end @@ -1260,7 +1260,7 @@ defmodule AshPostgres.DataLayer do end result = - with_savepoint(repo, fn -> + with_savepoint(repo, opts[:on_conflict], fn -> repo.insert_all(source, ecto_changesets, opts) end) @@ -1301,7 +1301,15 @@ defmodule AshPostgres.DataLayer do end end - defp with_savepoint(repo, fun) do + defp with_savepoint( + repo, + %{ + __ash_bindings__: %{ + expression_accumulator: %AshPostgres.Expr.ExprInfo{has_error?: true} + } + }, + fun + ) do if repo.in_transaction?() do savepoint_id = "a" <> (Ash.UUID.generate() |> String.replace("-", "_")) @@ -1325,15 +1333,14 @@ defmodule AshPostgres.DataLayer do result end else - try do - fun.() - rescue - e -> - reraise e, __STACKTRACE__ - end + fun.() end end + defp with_savepoint(_repo, _acc, fun) do + fun.() + end + defp upsert_set(resource, changesets, options) do attributes_changing_anywhere = changesets |> Enum.flat_map(&Map.keys(&1.attributes)) |> Enum.uniq() @@ -2102,7 +2109,7 @@ defmodule AshPostgres.DataLayer do repo = dynamic_repo(resource, changeset) result = - with_savepoint(repo, fn -> + with_savepoint(repo, query, fn -> repo.update_all( query, [], @@ -2183,9 +2190,9 @@ defmodule AshPostgres.DataLayer do ), {:ok, query} <- AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0), - dynamic <- + {dynamic, acc} <- AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__) do - {:cont, {:ok, query, Keyword.put(set, field, dynamic)}} + {:cont, {:ok, merge_expr_accumulator(query, acc), Keyword.put(set, field, dynamic)}} else other -> {:halt, other} @@ -2201,13 +2208,13 @@ defmodule AshPostgres.DataLayer do {[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} end) - {params, set, _} = + {params, set, _, query} = Enum.reduce( dynamics ++ existing_set, - {params, set, count}, - fn {key, value}, {params, set, count} -> + {params, set, count, query}, + fn {key, value}, {params, set, count, query} -> case AshPostgres.Expr.dynamic_expr(query, value, query.__ash_bindings__) do - %Ecto.Query.DynamicExpr{} = dynamic -> + {%Ecto.Query.DynamicExpr{} = dynamic, acc} -> result = Ecto.Query.Builder.Dynamic.partially_expand( :select, @@ -2223,10 +2230,11 @@ defmodule AshPostgres.DataLayer do new_count = result |> Tuple.to_list() |> List.last() - {new_params, [{key, expr} | set], new_count} + {new_params, [{key, expr} | set], new_count, merge_expr_accumulator(query, acc)} - other -> - {[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} + {other, acc} -> + {[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1, + merge_expr_accumulator(query, acc)} end end ) @@ -2260,12 +2268,10 @@ defmodule AshPostgres.DataLayer do repo = dynamic_repo(resource, changeset) result = - with_savepoint(repo, fn -> - repo.delete( - ecto_changeset, - repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - ) - end) + repo.delete( + ecto_changeset, + repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + ) result |> from_ecto() @@ -2351,11 +2357,11 @@ defmodule AshPostgres.DataLayer do def distinct(query, distinct_on, resource) do case get_distinct_statement(query, distinct_on) do - {:ok, distinct_statement} -> + {:ok, distinct_statement, query} -> %{query | distinct: distinct_statement} |> apply_sort(query.__ash_bindings__[:sort], resource) - {:error, distinct_statement} -> + {:error, {distinct_statement, query}} -> query |> Ecto.Query.exclude(:order_by) |> default_bindings(resource) @@ -2464,18 +2470,19 @@ defmodule AshPostgres.DataLayer do {:ok, default_distinct_statement(query, distinct_on)} else distinct_on - |> Enum.reduce_while({sort, [], [], Enum.count(distinct.params)}, fn - _, {[], _distinct_statement, _, _count} -> + |> Enum.reduce_while({sort, [], [], Enum.count(distinct.params), query}, fn + _, {[], _distinct_statement, _, _count, _query} -> {:halt, :error} - distinct_on, {[order_by | rest_order_by], distinct_statement, params, count} -> + distinct_on, {[order_by | rest_order_by], distinct_statement, params, count, query} -> case order_by do {^distinct_on, order} -> - {distinct_expr, params, count} = + {distinct_expr, params, count, query} = distinct_on_expr(query, distinct_on, params, count) {:cont, - {rest_order_by, [{order, distinct_expr} | distinct_statement], params, count}} + {rest_order_by, [{order, distinct_expr} | distinct_statement], params, count, + query}} _ -> {:halt, :error} @@ -2485,13 +2492,13 @@ defmodule AshPostgres.DataLayer do :error -> {:error, default_distinct_statement(query, distinct_on)} - {_, result, params, _} -> + {_, result, params, _, query} -> {:ok, %{ distinct | expr: distinct.expr ++ Enum.reverse(result), params: distinct.params ++ Enum.reverse(params) - }} + }, query} end end end @@ -2504,26 +2511,26 @@ defmodule AshPostgres.DataLayer do expr: [] } - {expr, params, _} = - Enum.reduce(distinct_on, {[], [], Enum.count(distinct.params)}, fn - {distinct_on_field, order}, {expr, params, count} -> - {distinct_expr, params, count} = + {expr, params, _, query} = + Enum.reduce(distinct_on, {[], [], Enum.count(distinct.params), query}, fn + {distinct_on_field, order}, {expr, params, count, query} -> + {distinct_expr, params, count, query} = distinct_on_expr(query, distinct_on_field, params, count) - {[{order, distinct_expr} | expr], params, count} + {[{order, distinct_expr} | expr], params, count, query} - distinct_on_field, {expr, params, count} -> - {distinct_expr, params, count} = + distinct_on_field, {expr, params, count, query} -> + {distinct_expr, params, count, query} = distinct_on_expr(query, distinct_on_field, params, count) - {[{:asc, distinct_expr} | expr], params, count} + {[{:asc, distinct_expr} | expr], params, count, query} end) - %{ - distinct - | expr: distinct.expr ++ Enum.reverse(expr), - params: distinct.params ++ Enum.reverse(params) - } + {%{ + distinct + | expr: distinct.expr ++ Enum.reverse(expr), + params: distinct.params ++ Enum.reverse(params) + }, query} end defp distinct_on_expr(query, field, params, count) do @@ -2542,7 +2549,7 @@ defmodule AshPostgres.DataLayer do } end - dynamic = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__) + {dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__) result = Ecto.Query.Builder.Dynamic.partially_expand( @@ -2557,7 +2564,7 @@ defmodule AshPostgres.DataLayer do new_params = elem(result, 1) new_count = result |> Tuple.to_list() |> List.last() - {expr, new_params, new_count} + {expr, new_params, new_count, merge_expr_accumulator(query, acc)} end @impl true @@ -2610,6 +2617,7 @@ defmodule AshPostgres.DataLayer do Map.put_new(query, :__ash_bindings__, %{ resource: resource, current: Enum.count(query.joins) + 1 + start_bindings, + expression_accumulator: %AshPostgres.Expr.ExprInfo{}, in_group?: false, calculations: %{}, parent_resources: [], @@ -2631,6 +2639,14 @@ defmodule AshPostgres.DataLayer do AshPostgres.Calculation.add_calculations(query, calculations, resource, 0, select?) end + @doc false + def merge_expr_accumulator(query, acc) do + update_in( + query.__ash_bindings__.expression_accumulator, + &AshPostgres.Expr.merge_accumulator(&1, acc) + ) + end + @doc false def get_binding(resource, path, query, type, name_match \\ nil) @@ -2666,9 +2682,11 @@ defmodule AshPostgres.DataLayer do filter |> split_and_statements() |> Enum.reduce(query, fn filter, query -> - dynamic = AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__) + {dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__) - Ecto.Query.where(query, ^dynamic) + query + |> Ecto.Query.where(^dynamic) + |> merge_expr_accumulator(acc) end) end diff --git a/lib/expr.ex b/lib/expr.ex index f4fd42b0..874ecc94 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -28,32 +28,39 @@ defmodule AshPostgres.Expr do require Ecto.Query - def dynamic_expr(query, expr, bindings, embedded? \\ false, type \\ nil) + defmodule ExprInfo do + @moduledoc false + defstruct has_error?: false + end + + def dynamic_expr(query, expr, bindings, embedded? \\ false, type \\ nil, acc \\ %ExprInfo{}) - def dynamic_expr(query, %Filter{expression: expression}, bindings, embedded?, type) do - dynamic_expr(query, expression, bindings, embedded?, type) + def dynamic_expr(query, %Filter{expression: expression}, bindings, embedded?, type, acc) do + dynamic_expr(query, expression, bindings, embedded?, type, acc) end # A nil filter means "everything" - def dynamic_expr(_, nil, _, _, _), do: true + def dynamic_expr(_, nil, _, _, _, acc), do: {true, acc} # A true filter means "everything" - def dynamic_expr(_, true, _, _, _), do: true + def dynamic_expr(_, true, _, _, _, acc), do: {true, acc} # A false filter means "nothing" - def dynamic_expr(_, false, _, _, _), do: false + def dynamic_expr(_, false, _, _, _, acc), do: {false, acc} - def dynamic_expr(query, expression, bindings, embedded?, type) do - do_dynamic_expr(query, expression, bindings, embedded?, type) + def dynamic_expr(query, expression, bindings, embedded?, type, acc) do + do_dynamic_expr(query, expression, bindings, embedded?, acc, type) end - defp do_dynamic_expr(query, expr, bindings, embedded?, type \\ nil) + defp do_dynamic_expr(query, expr, bindings, embedded?, acc, type \\ nil) - defp do_dynamic_expr(_, {:embed, other}, _bindings, _true, _type) do - other + defp do_dynamic_expr(_, {:embed, other}, _bindings, _true, acc, _type) do + {other, acc} end - defp do_dynamic_expr(query, %Not{expression: expression}, bindings, embedded?, _type) do - new_expression = do_dynamic_expr(query, expression, bindings, embedded?, :boolean) - Ecto.Query.dynamic(not (^new_expression)) + defp do_dynamic_expr(query, %Not{expression: expression}, bindings, embedded?, acc, _type) do + {new_expression, acc} = + do_dynamic_expr(query, expression, bindings, embedded?, acc, :boolean) + + {Ecto.Query.dynamic(not (^new_expression)), acc} end defp do_dynamic_expr( @@ -61,12 +68,16 @@ defmodule AshPostgres.Expr do %TrigramSimilarity{arguments: [arg1, arg2], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) do - arg1 = do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string) - arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string) + {arg1, acc} = + do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) + + {arg2, acc} = + do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) - Ecto.Query.dynamic(fragment("similarity(?, ?)", ^arg1, ^arg2)) + {Ecto.Query.dynamic(fragment("similarity(?, ?)", ^arg1, ^arg2)), acc} end defp do_dynamic_expr( @@ -74,12 +85,16 @@ defmodule AshPostgres.Expr do %VectorCosineDistance{arguments: [arg1, arg2], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) do - arg1 = do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string) - arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string) + {arg1, acc} = + do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) - Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)) + {arg2, acc} = + do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) + + {Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)), acc} end defp do_dynamic_expr( @@ -87,12 +102,16 @@ defmodule AshPostgres.Expr do %Like{arguments: [arg1, arg2], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) do - arg1 = do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string) - arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string) + {arg1, acc} = + do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) + + {arg2, acc} = + do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) - Ecto.Query.dynamic(like(^arg1, ^arg2)) + {Ecto.Query.dynamic(like(^arg1, ^arg2)), acc} end defp do_dynamic_expr( @@ -100,18 +119,25 @@ defmodule AshPostgres.Expr do %ILike{arguments: [arg1, arg2], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do - arg1 = do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string) - arg2 = do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string) + {arg1, acc} = + do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) + + {arg2, acc} = + do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) type = if type != Ash.Type.Boolean do type end - Ecto.Query.dynamic(ilike(^arg1, ^arg2)) - |> maybe_type(type, query) + { + Ecto.Query.dynamic(ilike(^arg1, ^arg2)) + |> maybe_type(type, query), + acc + } end defp do_dynamic_expr( @@ -119,11 +145,15 @@ defmodule AshPostgres.Expr do %IsNil{left: left, right: right, embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) do - left_expr = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?) - right_expr = do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, :boolean) - Ecto.Query.dynamic(is_nil(^left_expr) == ^right_expr) + {left_expr, acc} = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) + + {right_expr, acc} = + do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc, :boolean) + + {Ecto.Query.dynamic(is_nil(^left_expr) == ^right_expr), acc} end defp do_dynamic_expr( @@ -131,14 +161,16 @@ defmodule AshPostgres.Expr do %Ago{arguments: [left, right], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) when is_binary(right) or is_atom(right) do - left = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, :integer) + {left, acc} = + do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, :integer) - Ecto.Query.dynamic( - fragment("(?)", datetime_add(^DateTime.utc_now(), ^left * -1, ^to_string(right))) - ) + {Ecto.Query.dynamic( + fragment("(?)", datetime_add(^DateTime.utc_now(), ^left * -1, ^to_string(right))) + ), acc} end defp do_dynamic_expr( @@ -146,16 +178,23 @@ defmodule AshPostgres.Expr do %At{arguments: [left, right], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) do - left = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, :integer) - right = do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, :integer) + {left, acc} = + do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, :integer) - if is_integer(right) do - Ecto.Query.dynamic(fragment("(?)[?]", ^left, ^(right + 1))) - else - Ecto.Query.dynamic(fragment("(?)[? + 1]", ^left, ^right)) - end + {right, acc} = + do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc, :integer) + + expr = + if is_integer(right) do + Ecto.Query.dynamic(fragment("(?)[?]", ^left, ^(right + 1))) + else + Ecto.Query.dynamic(fragment("(?)[? + 1]", ^left, ^right)) + end + + {expr, acc} end defp do_dynamic_expr( @@ -163,14 +202,16 @@ defmodule AshPostgres.Expr do %FromNow{arguments: [left, right], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) when is_binary(right) or is_atom(right) do - left = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, :integer) + {left, acc} = + do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, :integer) - Ecto.Query.dynamic( - fragment("(?)", datetime_add(^DateTime.utc_now(), ^left, ^to_string(right))) - ) + {Ecto.Query.dynamic( + fragment("(?)", datetime_add(^DateTime.utc_now(), ^left, ^to_string(right))) + ), acc} end defp do_dynamic_expr( @@ -178,12 +219,17 @@ defmodule AshPostgres.Expr do %DateTimeAdd{arguments: [datetime, amount, interval], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) when is_binary(interval) or is_atom(interval) do - datetime = do_dynamic_expr(query, datetime, bindings, pred_embedded? || embedded?) - amount = do_dynamic_expr(query, amount, bindings, pred_embedded? || embedded?, :integer) - Ecto.Query.dynamic(fragment("(?)", datetime_add(^datetime, ^amount, ^to_string(interval)))) + {datetime, acc} = do_dynamic_expr(query, datetime, bindings, pred_embedded? || embedded?, acc) + + {amount, acc} = + do_dynamic_expr(query, amount, bindings, pred_embedded? || embedded?, acc, :integer) + + {Ecto.Query.dynamic(fragment("(?)", datetime_add(^datetime, ^amount, ^to_string(interval)))), + acc} end defp do_dynamic_expr( @@ -191,12 +237,16 @@ defmodule AshPostgres.Expr do %DateAdd{arguments: [date, amount, interval], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) when is_binary(interval) or is_atom(interval) do - date = do_dynamic_expr(query, date, bindings, pred_embedded? || embedded?) - amount = do_dynamic_expr(query, amount, bindings, pred_embedded? || embedded?, :integer) - Ecto.Query.dynamic(fragment("(?)", datetime_add(^date, ^amount, ^to_string(interval)))) + {date, acc} = do_dynamic_expr(query, date, bindings, pred_embedded? || embedded?, acc) + + {amount, acc} = + do_dynamic_expr(query, amount, bindings, pred_embedded? || embedded?, acc, :integer) + + {Ecto.Query.dynamic(fragment("(?)", datetime_add(^date, ^amount, ^to_string(interval)))), acc} end defp do_dynamic_expr( @@ -207,13 +257,14 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, _ ) when is_list(right) do type |> split_at_paths(constraints, right) - |> Enum.reduce(do_dynamic_expr(query, left, bindings, embedded?), fn data, expr -> - do_get_path(query, expr, data, bindings, embedded?, pred_embedded?) + |> Enum.reduce(do_dynamic_expr(query, left, bindings, embedded?, acc), fn data, {expr, acc} -> + do_get_path(query, expr, data, bindings, embedded?, pred_embedded?, acc) end) end @@ -222,6 +273,7 @@ defmodule AshPostgres.Expr do %Contains{arguments: [left, %Ash.CiString{} = right], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do if "citext" in AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate).installed_extensions() do @@ -239,6 +291,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) else @@ -256,6 +309,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -266,6 +320,7 @@ defmodule AshPostgres.Expr do %Contains{arguments: [left, right], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -282,6 +337,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -291,6 +347,7 @@ defmodule AshPostgres.Expr do %Length{arguments: [list], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -305,6 +362,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -314,6 +372,7 @@ defmodule AshPostgres.Expr do %If{arguments: [condition, when_true, when_false], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do [condition_type, when_true_type, when_false_type] = @@ -338,14 +397,35 @@ defmodule AshPostgres.Expr do [condition_type, when_true, when_false] end - condition = - do_dynamic_expr(query, condition, bindings, pred_embedded? || embedded?, condition_type) + {condition, acc} = + do_dynamic_expr( + query, + condition, + bindings, + pred_embedded? || embedded?, + acc, + condition_type + ) - when_true = - do_dynamic_expr(query, when_true, bindings, pred_embedded? || embedded?, when_true_type) + {when_true, acc} = + do_dynamic_expr( + query, + when_true, + bindings, + pred_embedded? || embedded?, + acc, + when_true_type + ) - {additional_cases, when_false} = - extract_cases(query, when_false, bindings, pred_embedded? || embedded?, when_false_type) + {additional_cases, when_false, acc} = + extract_cases( + query, + when_false, + bindings, + pred_embedded? || embedded?, + acc, + when_false_type + ) additional_case_fragments = additional_cases @@ -378,6 +458,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -387,6 +468,7 @@ defmodule AshPostgres.Expr do %StringJoin{arguments: [values, joiner], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -394,12 +476,13 @@ defmodule AshPostgres.Expr do %Fragment{ embedded?: pred_embedded?, arguments: - Enum.reduce(values, [raw: "(concat_ws(", expr: joiner], fn value, acc -> - acc ++ [raw: ", ", expr: value] + Enum.reduce(values, [raw: "(concat_ws(", expr: joiner], fn value, frag_acc -> + frag_acc ++ [raw: ", ", expr: value] end) ++ [raw: "))"] }, bindings, embedded?, + acc, type ) end @@ -409,6 +492,7 @@ defmodule AshPostgres.Expr do %StringSplit{arguments: [string, delimiter, options], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do if options[:trim?] do @@ -428,6 +512,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) else @@ -445,6 +530,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -455,6 +541,7 @@ defmodule AshPostgres.Expr do %StringJoin{arguments: [values], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -472,6 +559,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -483,6 +571,7 @@ defmodule AshPostgres.Expr do %Fragment{arguments: arguments, embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) do arguments = @@ -506,19 +595,20 @@ defmodule AshPostgres.Expr do arguments ++ [{:raw, ""}] end - {params, fragment_data, _} = - Enum.reduce(arguments, {[], [], 0}, fn - {:raw, str}, {params, fragment_data, count} -> - {params, [{:raw, str} | fragment_data], count} + {params, fragment_data, _, acc} = + Enum.reduce(arguments, {[], [], 0, acc}, fn + {:raw, str}, {params, fragment_data, count, acc} -> + {params, [{:raw, str} | fragment_data], count, acc} - {:casted_expr, dynamic}, {params, fragment_data, count} -> + {:casted_expr, dynamic}, {params, fragment_data, count, acc} -> {item, params, count} = {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} - {params, [{:expr, item} | fragment_data], count} + {params, [{:expr, item} | fragment_data], count, acc} - {:expr, expr}, {params, fragment_data, count} -> - dynamic = do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?) + {:expr, expr}, {params, fragment_data, count, acc} -> + {dynamic, acc} = + do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?, acc) type = if is_binary(expr) do @@ -530,17 +620,17 @@ defmodule AshPostgres.Expr do {item, params, count} = {{:^, [], [count]}, [{dynamic, type} | params], count + 1} - {params, [{:expr, item} | fragment_data], count} + {params, [{:expr, item} | fragment_data], count, acc} end) - %Ecto.Query.DynamicExpr{ - fun: fn _query -> - {{:fragment, [], Enum.reverse(fragment_data)}, Enum.reverse(params), [], %{}} - end, - binding: [], - file: __ENV__.file, - line: __ENV__.line - } + {%Ecto.Query.DynamicExpr{ + fun: fn _query -> + {{:fragment, [], Enum.reverse(fragment_data)}, Enum.reverse(params), [], %{}} + end, + binding: [], + file: __ENV__.file, + line: __ENV__.line + }, acc} end defp do_dynamic_expr( @@ -548,18 +638,22 @@ defmodule AshPostgres.Expr do %BooleanExpression{op: op, left: left, right: right}, bindings, embedded?, + acc, _type ) do - left_expr = do_dynamic_expr(query, left, bindings, embedded?, :boolean) - right_expr = do_dynamic_expr(query, right, bindings, embedded?, :boolean) + {left_expr, acc} = do_dynamic_expr(query, left, bindings, embedded?, acc, :boolean) + {right_expr, acc} = do_dynamic_expr(query, right, bindings, embedded?, acc, :boolean) - case op do - :and -> - Ecto.Query.dynamic(^left_expr and ^right_expr) + expr = + case op do + :and -> + Ecto.Query.dynamic(^left_expr and ^right_expr) - :or -> - Ecto.Query.dynamic(^left_expr or ^right_expr) - end + :or -> + Ecto.Query.dynamic(^left_expr or ^right_expr) + end + + {expr, acc} end defp do_dynamic_expr( @@ -567,14 +661,22 @@ defmodule AshPostgres.Expr do %Ash.Query.Function.Minus{arguments: [arg], embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do [determined_type] = AshPostgres.Types.determine_types(Ash.Query.Function.Minus, [arg]) - expr = - do_dynamic_expr(query, arg, bindings, pred_embedded? || embedded?, determined_type || type) + {expr, acc} = + do_dynamic_expr( + query, + arg, + bindings, + pred_embedded? || embedded?, + acc, + determined_type || type + ) - Ecto.Query.dynamic(-(^expr)) + {Ecto.Query.dynamic(-(^expr)), acc} end # Honestly we need to either 1. not type cast or 2. build in type compatibility concepts @@ -592,62 +694,66 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) do [left_type, right_type] = mod |> AshPostgres.Types.determine_types([left, right]) - left_expr = + {left_expr, acc} = if left_type && operator in @cast_operands_for do - left_expr = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?) + {left_expr, acc} = + do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) - Ecto.Query.dynamic(type(^left_expr, ^left_type)) + {Ecto.Query.dynamic(type(^left_expr, ^left_type)), acc} else - do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, left_type) + do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, left_type) end - right_expr = + {right_expr, acc} = if right_type && operator in @cast_operands_for do - right_expr = do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?) - Ecto.Query.dynamic(type(^right_expr, ^right_type)) + {right_expr, acc} = + do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc) + + {Ecto.Query.dynamic(type(^right_expr, ^right_type)), acc} else - do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, right_type) + do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc, right_type) end case operator do :== -> - Ecto.Query.dynamic(^left_expr == ^right_expr) + {Ecto.Query.dynamic(^left_expr == ^right_expr), acc} :!= -> - Ecto.Query.dynamic(^left_expr != ^right_expr) + {Ecto.Query.dynamic(^left_expr != ^right_expr), acc} :> -> - Ecto.Query.dynamic(^left_expr > ^right_expr) + {Ecto.Query.dynamic(^left_expr > ^right_expr), acc} :< -> - Ecto.Query.dynamic(^left_expr < ^right_expr) + {Ecto.Query.dynamic(^left_expr < ^right_expr), acc} :>= -> - Ecto.Query.dynamic(^left_expr >= ^right_expr) + {Ecto.Query.dynamic(^left_expr >= ^right_expr), acc} :<= -> - Ecto.Query.dynamic(^left_expr <= ^right_expr) + {Ecto.Query.dynamic(^left_expr <= ^right_expr), acc} :in -> - Ecto.Query.dynamic(^left_expr in ^right_expr) + {Ecto.Query.dynamic(^left_expr in ^right_expr), acc} :+ -> - Ecto.Query.dynamic(^left_expr + ^right_expr) + {Ecto.Query.dynamic(^left_expr + ^right_expr), acc} :- -> - Ecto.Query.dynamic(^left_expr - ^right_expr) + {Ecto.Query.dynamic(^left_expr - ^right_expr), acc} :/ -> - Ecto.Query.dynamic(type(^left_expr, :decimal) / type(^right_expr, :decimal)) + {Ecto.Query.dynamic(type(^left_expr, :decimal) / type(^right_expr, :decimal)), acc} :* -> - Ecto.Query.dynamic(^left_expr * ^right_expr) + {Ecto.Query.dynamic(^left_expr * ^right_expr), acc} :<> -> do_dynamic_expr( @@ -664,6 +770,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) @@ -684,6 +791,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) @@ -704,6 +812,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) @@ -712,8 +821,8 @@ defmodule AshPostgres.Expr do end end - defp do_dynamic_expr(query, %MapSet{} = mapset, bindings, embedded?, type) do - do_dynamic_expr(query, Enum.to_list(mapset), bindings, embedded?, type) + defp do_dynamic_expr(query, %MapSet{} = mapset, bindings, embedded?, acc, type) do + do_dynamic_expr(query, Enum.to_list(mapset), bindings, embedded?, acc, type) end defp do_dynamic_expr( @@ -721,9 +830,10 @@ defmodule AshPostgres.Expr do %Ash.CiString{string: string} = expression, bindings, embedded?, + acc, type ) do - string = do_dynamic_expr(query, string, bindings, embedded?) + {string, acc} = do_dynamic_expr(query, string, bindings, embedded?, acc) require_extension!(query, "citext", expression) @@ -739,6 +849,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) end @@ -751,6 +862,7 @@ defmodule AshPostgres.Expr do } = type_expr, bindings, embedded?, + acc, _type ) do calculation = %{calculation | load: calculation.name} @@ -794,6 +906,7 @@ defmodule AshPostgres.Expr do expression, bindings, embedded?, + acc, type ) @@ -817,6 +930,7 @@ defmodule AshPostgres.Expr do }, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -824,6 +938,7 @@ defmodule AshPostgres.Expr do %Ash.Query.Exists{path: agg_relationship_path, expr: true, at_path: ref_relationship_path}, bindings, embedded?, + acc, type ) end @@ -833,6 +948,7 @@ defmodule AshPostgres.Expr do %Ref{attribute: %Ash.Query.Aggregate{} = aggregate} = ref, bindings, _embedded?, + acc, _type ) do %{attribute: aggregate} = @@ -850,7 +966,7 @@ defmodule AshPostgres.Expr do first_optimized_aggregate? = AshPostgres.Aggregate.optimizable_first_aggregate?(related, aggregate) - {ref_binding, field_name, value} = + {ref_binding, field_name, value, acc} = if first_optimized_aggregate? do ref = %{ ref @@ -886,9 +1002,9 @@ defmodule AshPostgres.Expr do aggregate.context[:tracer] ) - value = dynamic_expr(query, ref, query.__ash_bindings__, false) + {value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc) - {ref_binding, aggregate.field, value} + {ref_binding, aggregate.field, value, acc} else ref_binding = ref_binding(ref, bindings) @@ -896,7 +1012,7 @@ defmodule AshPostgres.Expr do raise "Error while building reference: #{inspect(ref)}" end - {ref_binding, aggregate.name, nil} + {ref_binding, aggregate.name, nil, acc} end expr = @@ -932,9 +1048,9 @@ defmodule AshPostgres.Expr do end if type do - Ecto.Query.dynamic(type(^coalesced, ^type)) + {Ecto.Query.dynamic(type(^coalesced, ^type)), acc} else - coalesced + {coalesced, acc} end end @@ -943,6 +1059,7 @@ defmodule AshPostgres.Expr do %Type{arguments: [arg1, arg2, constraints]}, bindings, embedded?, + acc, _type ) do arg2 = Ash.Type.get_type(arg2) @@ -950,9 +1067,10 @@ defmodule AshPostgres.Expr do type = AshPostgres.Types.parameterized_type(arg2, constraints) if type do - Ecto.Query.dynamic(type(^do_dynamic_expr(query, arg1, bindings, embedded?, type), ^type)) + {expr, acc} = do_dynamic_expr(query, arg1, bindings, embedded?, acc, type) + {Ecto.Query.dynamic(type(^expr, ^type)), acc} else - do_dynamic_expr(query, arg1, bindings, embedded?, type) + do_dynamic_expr(query, arg1, bindings, embedded?, acc, type) end end @@ -961,6 +1079,7 @@ defmodule AshPostgres.Expr do %CompositeType{arguments: [arg1, arg2, constraints], embedded?: pred_embedded?}, bindings, embedded?, + acc, _type ) when is_map(arg1) do @@ -991,10 +1110,10 @@ defmodule AshPostgres.Expr do ] } - frag = - do_dynamic_expr(query, frag, bindings, embedded?) + {frag, acc} = + do_dynamic_expr(query, frag, bindings, embedded?, acc) - Ecto.Query.dynamic(type(^frag, ^type)) + {Ecto.Query.dynamic(type(^frag, ^type)), acc} end defp do_dynamic_expr( @@ -1002,6 +1121,7 @@ defmodule AshPostgres.Expr do %Now{embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -1009,6 +1129,7 @@ defmodule AshPostgres.Expr do DateTime.utc_now(), bindings, embedded? || pred_embedded?, + acc, type ) end @@ -1018,6 +1139,7 @@ defmodule AshPostgres.Expr do %Today{embedded?: pred_embedded?}, bindings, embedded?, + acc, type ) do do_dynamic_expr( @@ -1025,6 +1147,7 @@ defmodule AshPostgres.Expr do Date.utc_today(), bindings, embedded? || pred_embedded?, + acc, type ) end @@ -1034,6 +1157,7 @@ defmodule AshPostgres.Expr do %Ash.Query.Parent{expr: expr}, bindings, embedded?, + acc, type ) do parent? = Map.get(bindings.parent_bindings, :parent_is_parent_as?, true) @@ -1047,6 +1171,7 @@ defmodule AshPostgres.Expr do expr, new_bindings, embedded?, + acc, type ) end @@ -1056,10 +1181,13 @@ defmodule AshPostgres.Expr do %Error{arguments: [exception, input]} = value, _bindings, _embedded?, + acc, type ) do require_ash_functions!(query, "error/2") + acc = %{acc | has_error?: true} + unless Keyword.keyword?(input) || is_map(input) do raise "Input expression to `error` must be a map or keyword list" end @@ -1076,9 +1204,9 @@ defmodule AshPostgres.Expr do dynamic = Ecto.Query.dynamic(type(^nil, ^type)) - Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb, ?)", ^encoded, ^dynamic)) + {Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb, ?)", ^encoded, ^dynamic)), acc} else - Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb)", ^encoded)) + {Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb)", ^encoded)), acc} end end @@ -1087,6 +1215,7 @@ defmodule AshPostgres.Expr do %Exists{at_path: at_path, path: [first | rest], expr: expr}, bindings, _embedded?, + acc, _type ) do resource = Ash.Resource.Info.related(bindings.resource, at_path) @@ -1113,7 +1242,7 @@ defmodule AshPostgres.Expr do %Ash.Filter{expression: expr, resource: first_relationship.destination} |> nest_expression(rest) - {:ok, source} = + {:ok, source, source_acc} = AshPostgres.Join.maybe_get_resource_query( first_relationship.destination, first_relationship, @@ -1122,6 +1251,8 @@ defmodule AshPostgres.Expr do [first_relationship.name] ) + acc = merge_accumulator(acc, source_acc) + used_calculations = Ash.Filter.used_calculations( filter, @@ -1162,9 +1293,11 @@ defmodule AshPostgres.Expr do {:error, error} end + acc = merge_accumulator(query.__ash_bindings__.expression_accumulator, acc) + free_binding = filtered.__ash_bindings__.current - exists_query = + {exists_query, acc} = cond do Map.get(first_relationship, :manual) -> {module, opts} = first_relationship.manual @@ -1191,7 +1324,7 @@ defmodule AshPostgres.Expr do filtered ) - subquery + {subquery, acc} first_relationship.type == :many_to_many -> source_ref = @@ -1220,7 +1353,7 @@ defmodule AshPostgres.Expr do free_binding => %{path: [], source: first_relationship.through, type: :root} }) - {:ok, through} = + {:ok, through, through_acc} = AshPostgres.Join.maybe_get_resource_query( first_relationship.through, through_relationship, @@ -1232,19 +1365,24 @@ defmodule AshPostgres.Expr do false ) - Ecto.Query.from(destination in filtered, - join: through in ^through, - as: ^free_binding, - on: - field(through, ^first_relationship.destination_attribute_on_join_resource) == - field(destination, ^first_relationship.destination_attribute), - on: - field(parent_as(^source_ref), ^first_relationship.source_attribute) == - field(through, ^first_relationship.source_attribute_on_join_resource) - ) + acc = merge_accumulator(acc, through_acc) + + query = + Ecto.Query.from(destination in filtered, + join: through in ^through, + as: ^free_binding, + on: + field(through, ^first_relationship.destination_attribute_on_join_resource) == + field(destination, ^first_relationship.destination_attribute), + on: + field(parent_as(^source_ref), ^first_relationship.source_attribute) == + field(through, ^first_relationship.source_attribute_on_join_resource) + ) + + {query, acc} Map.get(first_relationship, :no_attributes?) -> - filtered + {filtered, acc} true -> source_ref = @@ -1258,11 +1396,14 @@ defmodule AshPostgres.Expr do bindings ) - Ecto.Query.from(destination in filtered, - where: - field(parent_as(^source_ref), ^first_relationship.source_attribute) == - field(destination, ^first_relationship.destination_attribute) - ) + query = + Ecto.Query.from(destination in filtered, + where: + field(parent_as(^source_ref), ^first_relationship.source_attribute) == + field(destination, ^first_relationship.destination_attribute) + ) + + {query, acc} end exists_query = @@ -1271,7 +1412,7 @@ defmodule AshPostgres.Expr do |> Ecto.Query.select(1) |> AshPostgres.DataLayer.set_subquery_prefix(query, first_relationship.destination) - Ecto.Query.dynamic(exists(Ecto.Query.subquery(exists_query))) + {Ecto.Query.dynamic(exists(Ecto.Query.subquery(exists_query))), acc} end defp do_dynamic_expr( @@ -1285,6 +1426,7 @@ defmodule AshPostgres.Expr do } = ref, bindings, _embedded?, + acc, expr_type ) do ref_binding = ref_binding(ref, bindings) @@ -1298,89 +1440,94 @@ defmodule AshPostgres.Expr do constraints end - case AshPostgres.Types.parameterized_type(attr_type || expr_type, constraints) do - nil -> - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(field(parent_as(^ref_binding), ^name)) - else - Ecto.Query.dynamic(field(as(^ref_binding), ^name)) - end + expr = + case AshPostgres.Types.parameterized_type(attr_type || expr_type, constraints) do + nil -> + if query.__ash_bindings__[:parent?] do + Ecto.Query.dynamic(field(parent_as(^ref_binding), ^name)) + else + Ecto.Query.dynamic(field(as(^ref_binding), ^name)) + end - type -> - validate_type!(query, type, ref) + type -> + validate_type!(query, type, ref) - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(type(field(parent_as(^ref_binding), ^name), ^type)) - else - Ecto.Query.dynamic(type(field(as(^ref_binding), ^name), ^type)) - end - end + if query.__ash_bindings__[:parent?] do + Ecto.Query.dynamic(type(field(parent_as(^ref_binding), ^name), ^type)) + else + Ecto.Query.dynamic(type(field(as(^ref_binding), ^name), ^type)) + end + end + + {expr, acc} end - defp do_dynamic_expr(_query, %Ash.Vector{} = value, _bindings, _embedded?, _type) do - value + defp do_dynamic_expr(_query, %Ash.Vector{} = value, _bindings, _embedded?, acc, _type) do + {value, acc} end - defp do_dynamic_expr(query, value, bindings, embedded?, _type) + defp do_dynamic_expr(query, value, bindings, embedded?, acc, _type) when is_map(value) and not is_struct(value) do - Map.new(value, fn {key, value} -> - {key, do_dynamic_expr(query, value, bindings, embedded?)} + Enum.reduce(value, {%{}, acc}, fn {key, value}, {map, acc} -> + {value, acc} = do_dynamic_expr(query, value, bindings, embedded?, acc) + {Map.put(map, key, value), acc} end) end - defp do_dynamic_expr(query, other, bindings, true, type) do + defp do_dynamic_expr(query, other, bindings, true, acc, type) do if other && is_atom(other) && !is_boolean(other) do - to_string(other) + {to_string(other), acc} else if Ash.Filter.TemplateHelpers.expr?(other) do if is_list(other) do - list_expr(query, other, bindings, true, type) + list_expr(query, other, bindings, true, acc, type) else raise "Unsupported expression in AshPostgres query: #{inspect(other)}" end else - maybe_sanitize_list(query, other, bindings, true, type) + maybe_sanitize_list(query, other, bindings, true, acc, type) end end end - defp do_dynamic_expr(query, value, bindings, embedded?, {:in, type}) when is_list(value) do - list_expr(query, value, bindings, embedded?, {:array, type}) + defp do_dynamic_expr(query, value, bindings, embedded?, acc, {:in, type}) when is_list(value) do + list_expr(query, value, bindings, embedded?, acc, {:array, type}) end - defp do_dynamic_expr(query, value, bindings, embedded?, type) + defp do_dynamic_expr(query, value, bindings, embedded?, acc, type) when not is_nil(value) and is_atom(value) and not is_boolean(value) do - do_dynamic_expr(query, to_string(value), bindings, embedded?, type) + do_dynamic_expr(query, to_string(value), bindings, embedded?, acc, type) end - defp do_dynamic_expr(query, value, bindings, false, type) when type == nil or type == :any do + defp do_dynamic_expr(query, value, bindings, false, acc, type) + when type == nil or type == :any do if is_list(value) do - list_expr(query, value, bindings, false, type) + list_expr(query, value, bindings, false, acc, type) else - maybe_sanitize_list(query, value, bindings, true, type) + maybe_sanitize_list(query, value, bindings, true, acc, type) end end - defp do_dynamic_expr(query, value, bindings, false, type) do + defp do_dynamic_expr(query, value, bindings, false, acc, type) do if Ash.Filter.TemplateHelpers.expr?(value) do if is_list(value) do - list_expr(query, value, bindings, false, type) + list_expr(query, value, bindings, false, acc, type) else raise "Unsupported expression in AshPostgres query: #{inspect(value)}" end else - case maybe_sanitize_list(query, value, bindings, true, type) do - ^value -> + case maybe_sanitize_list(query, value, bindings, true, acc, type) do + {^value, acc} -> if type do validate_type!(query, type, value) - Ecto.Query.dynamic(type(^value, ^type)) + {Ecto.Query.dynamic(type(^value, ^type)), acc} else - value + {value, acc} end - value -> - value + {value, acc} -> + {value, acc} end end end @@ -1390,8 +1537,9 @@ defmodule AshPostgres.Expr do expr, bindings, embedded?, + acc, type, - acc \\ [] + list_acc \\ [] ) defp extract_cases( @@ -1399,8 +1547,9 @@ defmodule AshPostgres.Expr do %If{arguments: [condition, when_true, when_false], embedded?: pred_embedded?}, bindings, embedded?, + acc, type, - acc + list_acc ) do [condition_type, when_true_type, when_false_type] = case AshPostgres.Types.determine_types(If, [condition, when_true, when_false]) do @@ -1424,19 +1573,34 @@ defmodule AshPostgres.Expr do [condition_type, when_true, when_false] end - condition = - do_dynamic_expr(query, condition, bindings, pred_embedded? || embedded?, condition_type) + {condition, acc} = + do_dynamic_expr( + query, + condition, + bindings, + pred_embedded? || embedded?, + acc, + condition_type + ) - when_true = - do_dynamic_expr(query, when_true, bindings, pred_embedded? || embedded?, when_true_type) + {when_true, acc} = + do_dynamic_expr( + query, + when_true, + bindings, + pred_embedded? || embedded?, + acc, + when_true_type + ) extract_cases( query, when_false, bindings, embedded?, + acc, when_false_type, - [{condition, when_true} | acc] + [{condition, when_true} | list_acc] ) end @@ -1445,19 +1609,21 @@ defmodule AshPostgres.Expr do other, bindings, embedded?, + acc, type, - acc + list_acc ) do - expr = + {expr, acc} = do_dynamic_expr( query, other, bindings, embedded?, + acc, type ) - {Enum.reverse(acc), expr} + {Enum.reverse(list_acc), expr, acc} end defp split_at_paths(type, constraints, next, acc \\ [{:bracket, [], nil, nil}]) @@ -1572,7 +1738,7 @@ defmodule AshPostgres.Expr do end end - defp list_expr(query, value, bindings, embedded?, type) do + defp list_expr(query, value, bindings, embedded?, acc, type) do type = case type do {:array, type} -> type @@ -1580,10 +1746,10 @@ defmodule AshPostgres.Expr do _ -> nil end - {params, exprs, _} = - Enum.reduce(value, {[], [], 0}, fn value, {params, data, count} -> - case do_dynamic_expr(query, value, bindings, embedded?, type) do - %Ecto.Query.DynamicExpr{} = dynamic -> + {params, exprs, _, acc} = + Enum.reduce(value, {[], [], 0, acc}, fn value, {params, data, count, acc} -> + case do_dynamic_expr(query, value, bindings, embedded?, acc, type) do + {%Ecto.Query.DynamicExpr{} = dynamic, acc} -> result = Ecto.Query.Builder.Dynamic.partially_expand( :select, @@ -1597,21 +1763,21 @@ defmodule AshPostgres.Expr do new_params = elem(result, 1) new_count = result |> Tuple.to_list() |> List.last() - {new_params, [expr | data], new_count} + {new_params, [expr | data], new_count, acc} - other -> - {params, [other | data], count} + {other, acc} -> + {params, [other | data], count, acc} end end) - %Ecto.Query.DynamicExpr{ - fun: fn _query -> - {Enum.reverse(exprs), Enum.reverse(params), [], []} - end, - binding: [], - file: __ENV__.file, - line: __ENV__.line - } + {%Ecto.Query.DynamicExpr{ + fun: fn _query -> + {Enum.reverse(exprs), Enum.reverse(params), [], []} + end, + binding: [], + file: __ENV__.file, + line: __ENV__.line + }, acc} end defp maybe_uuid_to_binary({:array, type}, value, _original_value) when is_list(value) do @@ -1656,11 +1822,19 @@ defmodule AshPostgres.Expr do Ecto.Query.dynamic(type(^dynamic, ^type)) end - defp maybe_sanitize_list(query, value, bindings, embedded?, type) do + defp maybe_sanitize_list(query, value, bindings, embedded?, acc, type) do if is_list(value) do - Enum.map(value, &do_dynamic_expr(query, &1, bindings, embedded?, type)) - else value + |> Enum.reduce({[], acc}, fn item, {list, acc} -> + {new_item, acc} = do_dynamic_expr(query, item, bindings, embedded?, acc, type) + + {[new_item | list], acc} + end) + |> then(fn {list, acc} -> + {Enum.reverse(list), acc} + end) + else + {value, acc} end end @@ -1692,7 +1866,8 @@ defmodule AshPostgres.Expr do {:bracket, path, type, constraints}, bindings, embedded?, - pred_embedded? + pred_embedded?, + acc ) do type = AshPostgres.Types.parameterized_type(type, constraints) path = path |> Enum.reverse() |> Enum.map(&to_string/1) @@ -1705,7 +1880,7 @@ defmodule AshPostgres.Expr do |> :lists.droplast() |> Enum.concat(raw: "::text)") - expr = + {expr, acc} = do_dynamic_expr( query, %Fragment{ @@ -1718,13 +1893,14 @@ defmodule AshPostgres.Expr do ] ++ path_frags }, bindings, - embedded? + embedded?, + acc ) if type do - Ecto.Query.dynamic(type(^expr, ^type)) + {Ecto.Query.dynamic(type(^expr, ^type)), acc} else - expr + {expr, acc} end end @@ -1734,12 +1910,13 @@ defmodule AshPostgres.Expr do {:dot, [field], type, constraints}, bindings, embedded?, - pred_embedded? + pred_embedded?, + acc ) when is_atom(field) do type = AshPostgres.Types.parameterized_type(type, constraints) - expr = + {expr, acc} = do_dynamic_expr( query, %Fragment{ @@ -1751,13 +1928,14 @@ defmodule AshPostgres.Expr do ] }, bindings, - embedded? + embedded?, + acc ) if type do - Ecto.Query.dynamic(type(^expr, ^type)) + {Ecto.Query.dynamic(type(^expr, ^type)), acc} else - expr + {expr, acc} end end @@ -1843,4 +2021,11 @@ defmodule AshPostgres.Expr do defp add_to_ref_path(%Ref{relationship_path: relationship_path} = ref, to_add) do %{ref | relationship_path: to_add ++ relationship_path} end + + @doc false + def merge_accumulator(%ExprInfo{has_error?: left_has_error?}, %ExprInfo{ + has_error?: right_has_error? + }) do + %ExprInfo{has_error?: left_has_error? || right_has_error?} + end end diff --git a/lib/join.ex b/lib/join.ex index a03d4bc2..3f5f14eb 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -248,14 +248,15 @@ defmodule AshPostgres.Join do query end - query = + {query, acc} = query |> do_base_filter( root_query, ash_query, resource, path, - bindings + bindings, + root_query.__ash_bindings__.expression_accumulator ) |> do_relationship_filter( relationship, @@ -267,7 +268,7 @@ defmodule AshPostgres.Join do is_subquery? ) - {:ok, query} + {:ok, query, acc} {:error, error} -> {:error, error} @@ -298,15 +299,16 @@ defmodule AshPostgres.Join do from(row in subquery(Ecto.Query.order_by(query, ^order_by)), []) |> AshPostgres.DataLayer.default_bindings(destination) + |> AshPostgres.DataLayer.merge_expr_accumulator(query.__ash_bindings__.expression_accumulator) |> Map.update!(:__ash_bindings__, &Map.put(&1, :current, query.__ash_bindings__.current)) end defp do_relationship_sort(query, _, _), do: query - defp do_relationship_filter(query, %{filter: nil}, _, _, _, _, _, _), do: query + defp do_relationship_filter({query, acc}, %{filter: nil}, _, _, _, _, _, _), do: {query, acc} defp do_relationship_filter( - query, + {query, acc}, relationship, root_query, ash_query, @@ -380,7 +382,7 @@ defmodule AshPostgres.Join do relationship.source | parent_bindings[:parent_resources] || [] ]) - dynamic = + {dynamic, acc} = if has_bindings? do filter = if is_subquery? do @@ -389,18 +391,18 @@ defmodule AshPostgres.Join do filter end - AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true) + AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true, acc) else - AshPostgres.Expr.dynamic_expr(query, filter, bindings, true) + AshPostgres.Expr.dynamic_expr(query, filter, bindings, true, acc) end - from(row in query, where: ^dynamic) + {from(row in query, where: ^dynamic), acc} end - defp do_base_filter(query, root_query, ash_query, resource, path, bindings) do + defp do_base_filter(query, root_query, ash_query, resource, path, bindings, acc) do case Ash.Resource.Info.base_filter(resource) do nil -> - query + {query, %AshPostgres.Expr.ExprInfo{}} filter -> filter = @@ -412,16 +414,16 @@ defmodule AshPostgres.Join do ash_query.context ) - dynamic = + {dynamic, acc} = if bindings do filter = Ash.Filter.move_to_relationship_path(filter, path) - AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true) + AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true, acc) else - AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__, true) + AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__, true, acc) end - from(row in query, where: ^dynamic) + {from(row in query, where: ^dynamic), acc} end end @@ -609,12 +611,14 @@ defmodule AshPostgres.Join do {:error, error} -> {:error, error} - {:ok, relationship_destination} -> + {:ok, relationship_destination, acc} -> relationship_destination = relationship_destination |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + binding_kinds = case kind do :left -> @@ -745,7 +749,7 @@ defmodule AshPostgres.Join do query.__ash_bindings__ end - with {:ok, relationship_through} <- + with {:ok, relationship_through, through_acc} <- maybe_get_resource_query( relationship.through, join_relationship, @@ -754,7 +758,7 @@ defmodule AshPostgres.Join do join_path, root_bindings ), - {:ok, relationship_destination} <- + {:ok, relationship_destination, dest_acc} <- maybe_get_resource_query( relationship.destination, relationship, @@ -763,6 +767,11 @@ defmodule AshPostgres.Join do path, root_bindings ) do + query = + query + |> AshPostgres.DataLayer.merge_expr_accumulator(through_acc) + |> AshPostgres.DataLayer.merge_expr_accumulator(dest_acc) + relationship_through = relationship_through |> Ecto.Queryable.to_query() @@ -914,7 +923,9 @@ defmodule AshPostgres.Join do {:error, error} -> {:error, error} - {:ok, relationship_destination} -> + {:ok, relationship_destination, acc} -> + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + relationship_destination = relationship_destination |> Ecto.Queryable.to_query() diff --git a/lib/sort.ex b/lib/sort.ex index 2b8af7f3..dee61655 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -71,8 +71,8 @@ defmodule AshPostgres.Sort do {:ok, query} -> sort |> sanitize_sort() - |> Enum.reduce_while({:ok, []}, fn - {order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr} -> + |> Enum.reduce_while({:ok, [], query}, fn + {order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr, query} -> type = if calc.type do AshPostgres.Types.parameterized_type(calc.type, calc.constraints) @@ -101,7 +101,7 @@ defmodule AshPostgres.Sort do query.__ash_bindings__ end - expr = + {expr, acc} = AshPostgres.Expr.dynamic_expr( query, expr, @@ -110,13 +110,15 @@ defmodule AshPostgres.Sort do type ) - {:cont, {:ok, query_expr ++ [{order, expr}]}} + {:cont, + {:ok, query_expr ++ [{order, expr}], + AshPostgres.DataLayer.merge_expr_accumulator(query, acc)}} {:error, error} -> {:halt, {:error, error}} end - {order, sort}, {:ok, query_expr} -> + {order, sort}, {:ok, query_expr, query} -> expr = case find_aggregate_binding( query.__ash_bindings__.bindings, @@ -183,17 +185,17 @@ defmodule AshPostgres.Sort do Ecto.Query.dynamic(field(as(^binding), ^sort)) end - {:cont, {:ok, query_expr ++ [{order, expr}]}} + {:cont, {:ok, query_expr ++ [{order, expr}], query}} end) |> case do - {:ok, []} -> + {:ok, [], query} -> if type == :return do {:ok, [], query} else {:ok, query} end - {:ok, sort_exprs} -> + {:ok, sort_exprs, query} -> case type do :return -> {:ok, order_to_fragments(sort_exprs), query} diff --git a/test/error_expr_test.exs b/test/error_expr_test.exs index ff723018..201c70d6 100644 --- a/test/error_expr_test.exs +++ b/test/error_expr_test.exs @@ -35,4 +35,23 @@ defmodule AshPostgres.ErrorExprTest do |> Enum.map(& &1.calculations) end end + + test "exceptions in calculations are treated as regular Ash exceptions in transactions" do + Post + |> Ash.Changeset.new(%{title: "title"}) + |> Api.create!() + + assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> + AshPostgres.TestRepo.transaction(fn -> + Post + |> Ash.Query.calculate( + :test, + expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), + :string + ) + |> Api.read!() + |> Enum.map(& &1.calculations) + end) + end + end end From 9bc5b2af9f0c7c8e961dedfedd970b536ae2f2e2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 21 Dec 2023 19:52:29 -0500 Subject: [PATCH 0133/1215] improvement: support string_length and string_trim --- lib/expr.ex | 48 ++++++++++++++++++++++++ mix.exs | 2 +- mix.lock | 2 +- test/calculation_test.exs | 77 +++++++++------------------------------ 4 files changed, 68 insertions(+), 61 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 874ecc94..de0590f1 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -19,7 +19,9 @@ defmodule AshPostgres.Expr do Length, Now, StringJoin, + StringLength, StringSplit, + StringTrim, Today, Type } @@ -564,6 +566,52 @@ defmodule AshPostgres.Expr do ) end + defp do_dynamic_expr( + query, + %StringLength{arguments: [value], embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + type + ) do + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [raw: "length(", expr: value, raw: ")"] + }, + bindings, + embedded?, + acc, + type + ) + end + + defp do_dynamic_expr( + query, + %StringTrim{arguments: [value], embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + type + ) do + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "REGEXP_REPLACE(REGEXP_REPLACE(", + expr: value, + raw: ", '\s+$', ''), '^\s+', '')" + ] + }, + bindings, + embedded?, + acc, + type + ) + end + # Sorry :( # This is bad to do, but is the only reasonable way I could find. defp do_dynamic_expr( diff --git a/mix.exs b/mix.exs index d9ad9b67..10ee82a3 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.13")}, + {:ash, ash_version("~> 2.17 and >= 2.17.14")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 9e723186..a75eec54 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.14", "ccb68a0eacd7d4f4652a01baa3ceda510d793ab769c82958d644638905f08f7d", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b09a585924f222a1d353ebd13ec8691c4b9c5e37a8be271ee8960c34feb3fad0"}, + "ash": {:hex, :ash, "2.17.15", "5a71025ad4878c0522408032e5f6253b43dee19de50d0ef23ff57555f05ea646", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44082e414ee43dc2c15f31ec26121a626855587413eee2f5325e5e783a04f59b"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 1a5cb575..e4b55542 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -618,63 +618,22 @@ defmodule AshPostgres.CalculationTest do |> Enum.map(&Map.get(&1, :calc_returning_json)) end - # test "calculation passes actor to aggregate from calculation on aggregate" do - # org = - # Organization - # |> Ash.Changeset.new(%{name: "The Org"}) - # |> Api.create!() - - # user = - # User - # |> Ash.Changeset.for_create(:create, %{is_active: true}) - # |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) - # |> Api.create!() - - # profile = - # Profile - # |> Ash.Changeset.for_create(:create, %{description: "Prolific describer of worlds..."}) - # |> Api.create!() - - # author = - # Author - # |> Ash.Changeset.for_create(:create, %{ - # first_name: "Foo", - # bio: %{title: "Mr.", bio: "Bones"} - # }) - # |> Ash.Changeset.manage_relationship(:profile, profile, type: :append) - # |> Api.create!() - - # created_post = - # Post - # |> Ash.Changeset.new(%{title: "match"}) - # |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) - # |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - # |> Api.create!() - - # can_get_author_description_post = - # Post - # |> Ash.Query.filter(id == ^created_post.id) - # |> Ash.Query.load(author: :description) - # |> Api.read_one!(actor: user) - - # assert can_get_author_description_post.author.description == "Prolific describer of worlds..." - - # can_get_author_description_from_aggregate_post = - # Post - # |> Ash.Query.filter(id == ^created_post.id) - # |> Ash.Query.load(:author_profile_description) - # |> Api.read_one!(actor: user) - - # assert can_get_author_description_from_aggregate_post.author_profile_description == - # "Prolific describer of worlds..." - - # can_get_author_description_from_calculation_of_aggregate_post = - # Post - # |> Ash.Query.filter(id == ^created_post.id) - # |> Ash.Query.load(:author_profile_description_from_agg) - # |> Api.read_one!(actor: user) - - # assert can_get_author_description_from_calculation_of_aggregate_post.author_profile_description_from_agg == - # "Prolific describer of worlds..." - # end + test "string_length and string_trim work" do + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Bill", + last_name: "Jones", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Api.create!() + + assert %{calculations: %{length: 9}} = + Author + |> Ash.Query.calculate( + :length, + expr(string_length(string_trim(first_name <> last_name <> " "))), + :integer + ) + |> Api.read_one!() + end end From 5d246cc23b8a5576f230ebb442257004d8f01669 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Dec 2023 15:11:49 -0500 Subject: [PATCH 0134/1215] chore: take proper args from codegen args --- lib/data_layer.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 8ff0c54e..885e76d8 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -402,7 +402,15 @@ defmodule AshPostgres.DataLayer do end def codegen(args) do - # TODO: take args that we care about + {args, _, _} = OptionParser.parse(args, strict: [name: :string]) + + args = + if args[:name] do + ["--name", to_string(args[:name])] + else + [] + end + Mix.Task.run("ash_postgres.generate_migrations", args) end From 68e2f74fe0453a9a8a1c528015e7609930ad147e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Dec 2023 21:14:40 -0500 Subject: [PATCH 0135/1215] improvement: support aggregates using other aggregates fix: various fixes for unnecessary aggregate additions test: added a test to confirm context based multitenancy behavior --- lib/aggregate.ex | 87 ++++++++------------- lib/calculation.ex | 15 +--- lib/data_layer.ex | 42 +++------- lib/expr.ex | 54 ++++++++----- lib/join.ex | 57 +------------- mix.exs | 2 +- mix.lock | 2 +- test/calculation_test.exs | 23 ++++++ test/complex_calculations_test.exs | 27 +++++++ test/multitenancy_test.exs | 10 +++ test/support/multitenancy/resources/post.ex | 17 +++- test/support/resources/author.ex | 2 + test/support/resources/post.ex | 2 + 13 files changed, 168 insertions(+), 172 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 4afe5fb2..b098d406 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -24,18 +24,30 @@ defmodule AshPostgres.Aggregate do {:ok, aggregates} -> query = AshPostgres.DataLayer.default_bindings(query, resource) - {query, aggregates, aggregate_name_mapping} = - Enum.reduce(aggregates, {query, [], %{}}, fn aggregate, - {query, aggregates, aggregate_name_mapping} -> - if is_atom(aggregate.name) do - {query, [aggregate | aggregates], aggregate_name_mapping} - else - {query, name} = use_aggregate_name(query, aggregate.name) + # initial_aggregate_name_mapping = + # case root_data do + # {_, _, initial_aggregate_name_mapping} -> + # initial_aggregate_name_mapping + + # _ -> + # %{} + # end - {query, [%{aggregate | name: name} | aggregates], - Map.put(aggregate_name_mapping, name, aggregate.name)} + {query, aggregates, aggregate_name_mapping} = + Enum.reduce( + aggregates, + {query, [], %{}}, + fn aggregate, {query, aggregates, aggregate_name_mapping} -> + if is_atom(aggregate.name) do + {query, [aggregate | aggregates], aggregate_name_mapping} + else + {query, name} = use_aggregate_name(query, aggregate.name) + + {query, [%{aggregate | name: name} | aggregates], + Map.put(aggregate_name_mapping, name, aggregate.name)} + end end - end) + ) aggregates = Enum.reject(aggregates, fn aggregate -> @@ -386,8 +398,16 @@ defmodule AshPostgres.Aggregate do related = Ash.Resource.Info.related(first_relationship.destination, relationship_path) agg_query = - case Ash.Resource.Info.calculation(related, aggregate.field) do - %{name: name, calculation: {module, opts}, type: type, constraints: constraints} -> + case Ash.Resource.Info.field(related, aggregate.field) do + %Ash.Resource.Aggregate{} -> + raise "can't do this yet" + + %Ash.Resource.Calculation{ + name: name, + calculation: {module, opts}, + type: type, + constraints: constraints + } -> {:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, {type, constraints}) expression = module.expression(opts, aggregate.context) @@ -415,7 +435,7 @@ defmodule AshPostgres.Aggregate do agg_query - nil -> + _ -> agg_query end @@ -805,30 +825,6 @@ defmodule AshPostgres.Aggregate do defp has_sort?(%{sort: _}), do: true defp has_sort?(_), do: false - def used_aggregates(filter, resource, used_calculations, path) do - Ash.Filter.used_aggregates(filter, path) ++ - Enum.flat_map( - used_calculations, - fn calculation -> - case Ash.Filter.hydrate_refs( - calculation.module.expression(calculation.opts, calculation.context), - %{ - resource: resource, - aggregates: %{}, - calculations: %{}, - public?: false - } - ) do - {:ok, hydrated} -> - Ash.Filter.used_aggregates(hydrated) - - _ -> - [] - end - end - ) - end - def add_subquery_aggregate_select( query, relationship_path, @@ -1143,22 +1139,7 @@ defmodule AshPostgres.Aggregate do relationship_path ) - used_calculations = - Ash.Filter.used_calculations( - filter, - query.__ash_bindings__.resource - ) - - used_aggregates = - filter - |> AshPostgres.Aggregate.used_aggregates( - query.__ash_bindings__.resource, - used_calculations, - [] - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) + used_aggregates = Ash.Filter.used_aggregates(filter, []) {:ok, query} = AshPostgres.Join.join_all_relationships(query, filter) diff --git a/lib/calculation.ex b/lib/calculation.ex index 21d83286..bd4e0922 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -21,19 +21,8 @@ defmodule AshPostgres.Calculation do aggregates = calculations |> Enum.flat_map(fn {calculation, expression} -> - used_calculations = - Ash.Filter.used_calculations( - expression, - query.__ash_bindings__.resource, - [] - ) - - AshPostgres.Aggregate.used_aggregates( - expression, - query.__ash_bindings__.resource, - used_calculations, - [] - ) + expression + |> Ash.Filter.used_aggregates([]) |> Enum.map(&Map.put(&1, :context, calculation.context)) end) |> Enum.uniq() diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 885e76d8..84dfba14 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1160,12 +1160,19 @@ defmodule AshPostgres.DataLayer do def set_subquery_prefix(data_layer_query, source_query, resource) do config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() + query_tenant = + case source_query do + %{__tenant__: tenant} -> tenant + %{tenant: tenant} -> tenant + _ -> nil + end + if Ash.Resource.Info.multitenancy_strategy(resource) == :context do %{ data_layer_query | prefix: to_string( - source_query.tenant || AshPostgres.DataLayer.Info.schema(resource) || + query_tenant || AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || "public" ) @@ -2170,22 +2177,8 @@ defmodule AshPostgres.DataLayer do atomics_result = Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> - used_calculations = - Ash.Filter.used_calculations( - expr, - resource - ) - used_aggregates = - expr - |> AshPostgres.Aggregate.used_aggregates( - resource, - used_calculations, - [] - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) + Ash.Filter.used_aggregates(expr, []) with {:ok, query} <- AshPostgres.Join.join_all_relationships( @@ -2579,22 +2572,7 @@ defmodule AshPostgres.DataLayer do def filter(query, filter, resource, opts \\ []) do query = default_bindings(query, resource) - used_calculations = - Ash.Filter.used_calculations( - filter, - resource - ) - - used_aggregates = - filter - |> AshPostgres.Aggregate.used_aggregates( - resource, - used_calculations, - [] - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) + used_aggregates = Ash.Filter.used_aggregates(filter, []) query |> AshPostgres.Join.join_all_relationships(filter, opts) diff --git a/lib/expr.ex b/lib/expr.ex index de0590f1..cc799f1e 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -16,6 +16,7 @@ defmodule AshPostgres.Expr do FromNow, GetPath, If, + Lazy, Length, Now, StringJoin, @@ -158,6 +159,17 @@ defmodule AshPostgres.Expr do {Ecto.Query.dynamic(is_nil(^left_expr) == ^right_expr), acc} end + defp do_dynamic_expr( + _query, + %Lazy{arguments: [{m, f, a}]}, + _bindings, + _embedded?, + acc, + _type + ) do + {apply(m, f, a), acc} + end + defp do_dynamic_expr( query, %Ago{arguments: [left, right], embedded?: pred_embedded?}, @@ -1063,6 +1075,20 @@ defmodule AshPostgres.Expr do {ref_binding, aggregate.name, nil, acc} end + field_name = + if is_binary(field_name) do + new_field_name = + query.__ash_bindings__.aggregate_names[field_name] + + unless new_field_name do + raise "Unbound aggregate field: #{inspect(field_name)}" + end + + new_field_name + else + field_name + end + expr = if value do value @@ -1301,23 +1327,7 @@ defmodule AshPostgres.Expr do acc = merge_accumulator(acc, source_acc) - used_calculations = - Ash.Filter.used_calculations( - filter, - first_relationship.destination, - [] - ) - - used_aggregates = - filter - |> AshPostgres.Aggregate.used_aggregates( - first_relationship.destination, - used_calculations, - [] - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) + used_aggregates = Ash.Filter.used_aggregates(filter, []) {:ok, filtered} = source @@ -1894,7 +1904,15 @@ defmodule AshPostgres.Expr do data.type == :aggregate && data.path == relationship_path && Enum.any?(data.aggregates, &(&1.name == name)) && binding - end) + end) || + Enum.find_value(bindings.bindings, fn {binding, data} -> + data.type in [:inner, :left, :root] && + Ash.SatSolver.synonymous_relationship_paths?( + bindings.resource, + data.path, + relationship_path + ) && binding + end) end defp ref_binding(%{attribute: %Ash.Resource.Attribute{}} = ref, bindings) do diff --git a/lib/join.ex b/lib/join.ex index 3f5f14eb..3a41c96c 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -268,7 +268,7 @@ defmodule AshPostgres.Join do is_subquery? ) - {:ok, query, acc} + {:ok, Map.put(query, :__tenant__, Map.get(root_query, :__tenant__)), acc} {:error, error} -> {:error, error} @@ -575,23 +575,7 @@ defmodule AshPostgres.Join do query = AshPostgres.DataLayer.add_binding(query, binding_data) - used_calculations = - Ash.Filter.used_calculations( - filter, - relationship.destination, - full_path - ) - - used_aggregates = - filter - |> AshPostgres.Aggregate.used_aggregates( - relationship.destination, - used_calculations, - full_path - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) + used_aggregates = Ash.Filter.used_aggregates(filter, full_path) use_root_query_bindings? = Enum.empty?(used_aggregates) @@ -724,23 +708,7 @@ defmodule AshPostgres.Join do {:ok, query} = join_all_relationships(query, related_filter) - used_calculations = - Ash.Filter.used_calculations( - filter, - relationship.destination, - full_path - ) - - used_aggregates = - filter - |> AshPostgres.Aggregate.used_aggregates( - relationship.destination, - used_calculations, - full_path - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) + used_aggregates = Ash.Filter.used_aggregates(filter, full_path) use_root_query_bindings? = Enum.empty?(used_aggregates) @@ -883,24 +851,7 @@ defmodule AshPostgres.Join do {:ok, query} = join_all_relationships(query, related_filter) - used_calculations = - Ash.Filter.used_calculations( - filter, - relationship.destination, - full_path - ) - - used_aggregates = - filter - |> AshPostgres.Aggregate.used_aggregates( - relationship.destination, - used_calculations, - full_path - ) - |> Enum.map(fn aggregate -> - %{aggregate | load: aggregate.name} - end) - + used_aggregates = Ash.Filter.used_aggregates(filter, full_path) use_root_query_bindings? = Enum.empty?(used_aggregates) needs_subquery? = Map.get(relationship, :from_many?, false) diff --git a/mix.exs b/mix.exs index 10ee82a3..e920efb8 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.14")}, + {:ash, ash_version("~> 2.17 and >= 2.17.17")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index a75eec54..ed2d82f1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.15", "5a71025ad4878c0522408032e5f6253b43dee19de50d0ef23ff57555f05ea646", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44082e414ee43dc2c15f31ec26121a626855587413eee2f5325e5e783a04f59b"}, + "ash": {:hex, :ash, "2.17.17", "437688358c4f3fe18087e47b16388f0cc6c1eaaadfe44c0eeef1ef7c871ed9f4", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df074e4246db04351344db23f36598535d568f7a91023eafd7af698e34804f0b"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index e4b55542..4b9e1c99 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -636,4 +636,27 @@ defmodule AshPostgres.CalculationTest do ) |> Api.read_one!() end + + test "lazy values are evaluated lazily" do + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Bill", + last_name: "Jones", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Api.create!() + + assert %{calculations: %{string: "fred"}} = + Author + |> Ash.Query.calculate( + :string, + expr(lazy({__MODULE__, :fred, []})), + :string + ) + |> Api.read_one!() + end + + def fred do + "fred" + end end diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index a82f393d..aae11a6f 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -195,4 +195,31 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do |> Ash.Query.filter(foo == "foobar") |> AshPostgres.Test.ComplexCalculations.Api.read!(load: :foo) end + + test "calculations with aggregates can be referenced from aggregates" do + author = + AshPostgres.Test.Author + |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) + |> AshPostgres.Test.Api.create!() + + AshPostgres.Test.Post + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> AshPostgres.Test.Api.create!() + + assert [%{author_count_of_posts: 1}] = + AshPostgres.Test.Post + |> Ash.Query.load(:author_count_of_posts) + |> AshPostgres.Test.Api.read!() + + assert [%{author_count_of_posts: 1}] = + AshPostgres.Test.Post + |> AshPostgres.Test.Api.read!() + |> AshPostgres.Test.Api.load!(:author_count_of_posts) + + assert [_] = + AshPostgres.Test.Post + |> Ash.Query.filter(author_count_of_posts == 1) + |> AshPostgres.Test.Api.read!() + end end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 6bbf3770..9fd56055 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -37,6 +37,16 @@ defmodule AshPostgres.Test.MultitenancyTest do |> Api.read!() end + test "context multitenancy works with policies", %{org1: org1} do + Post + |> Ash.Changeset.new(name: "foo") + |> Ash.Changeset.set_tenant(tenant(org1)) + |> Api.create!() + |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true) + |> Ash.Changeset.set_tenant(tenant(org1)) + |> Api.update!() + end + test "attribute multitenancy is set on creation" do uuid = Ash.UUID.generate() diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index 968f360b..963e54b0 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -1,7 +1,19 @@ defmodule AshPostgres.MultitenancyTest.Post do @moduledoc false use Ash.Resource, - data_layer: AshPostgres.DataLayer + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + policies do + policy always() do + authorize_if(always()) + end + + policy action(:update_with_policy) do + # this is silly, but we want to force it to make a query + authorize_if(expr(exists(self, true))) + end + end attributes do uuid_primary_key(:id, writable?: true) @@ -10,6 +22,8 @@ defmodule AshPostgres.MultitenancyTest.Post do actions do defaults([:create, :read, :update, :destroy]) + + update(:update_with_policy) end postgres do @@ -26,5 +40,6 @@ defmodule AshPostgres.MultitenancyTest.Post do relationships do belongs_to(:org, AshPostgres.MultitenancyTest.Org) belongs_to(:user, AshPostgres.MultitenancyTest.User) + has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id) end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 54455185..f84aa5fc 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -42,6 +42,8 @@ defmodule AshPostgres.Test.Author do ) ) + calculate(:count_of_posts_with_calc, :integer, expr(count(posts, []))) + calculate(:title, :string, expr(bio[:title])) calculate(:full_name, :string, expr(first_name <> " " <> last_name)) calculate(:full_name_with_nils, :string, expr(string_join([first_name, last_name], " "))) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c96f5778..2bc3f929 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -242,6 +242,8 @@ defmodule AshPostgres.Test.Post do ) ) + calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) + calculate( :price_string, :string, From 32ef8e6834bed521f00e4177c99a462e7b5477c9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Dec 2023 21:16:13 -0500 Subject: [PATCH 0136/1215] chore: remove commented out code --- lib/aggregate.ex | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index b098d406..7e3170ea 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -24,15 +24,6 @@ defmodule AshPostgres.Aggregate do {:ok, aggregates} -> query = AshPostgres.DataLayer.default_bindings(query, resource) - # initial_aggregate_name_mapping = - # case root_data do - # {_, _, initial_aggregate_name_mapping} -> - # initial_aggregate_name_mapping - - # _ -> - # %{} - # end - {query, aggregates, aggregate_name_mapping} = Enum.reduce( aggregates, From fb7f7ad40b98f13270b2598108984a0cee4888e5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Dec 2023 21:18:54 -0500 Subject: [PATCH 0137/1215] chore: release version v1.3.65 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ec57d6..466589dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.65](https://github.com/ash-project/ash_postgres/compare/v1.3.64...v1.3.65) (2023-12-23) + + + + +### Bug Fixes: + +* various fixes for unnecessary aggregate additions + +* use lateral joins when joining to subquery w/ parent reference + +* replace upsert field with source in EXCLUDED fragment (#187) + +* handle strings in get_path + +* reenable mix tasks that need calling + +### Improvements: + +* support aggregates using other aggregates + +* support string_length and string_trim + +* only start savepoints when necessary + +* clean up nested if statements to single case statements + +* support for `error/2` expression + ## [v1.3.64](https://github.com/ash-project/ash_postgres/compare/v1.3.63...v1.3.64) (2023-12-04) diff --git a/mix.exs b/mix.exs index e920efb8..0178cc50 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.64" + @version "1.3.65" def project do [ From 46118a77caeddf3fe0a0542a6da4466eccf5087c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Dec 2023 22:23:49 -0500 Subject: [PATCH 0138/1215] improvement: support aggregates as `get_path` subject --- lib/expr.ex | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/expr.ex b/lib/expr.ex index cc799f1e..2d8ea867 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -263,6 +263,48 @@ defmodule AshPostgres.Expr do {Ecto.Query.dynamic(fragment("(?)", datetime_add(^date, ^amount, ^to_string(interval)))), acc} end + defp do_dynamic_expr( + query, + %GetPath{ + arguments: [ + %Ref{attribute: %Ash.Resource.Aggregate{} = aggregate, resource: resource} = left, + right + ], + embedded?: pred_embedded? + }, + bindings, + embedded?, + acc, + _ + ) + when is_list(right) do + attribute = + if aggregate.field do + related = Ash.Resource.Info.related(resource, aggregate.relationship_path) + Ash.Resource.Info.attribute(related, aggregate.field) + end + + attribute_type = + if attribute do + attribute.type + end + + attribute_constraints = + if attribute do + attribute.constraints + end + + {:ok, type, constraints} = + Ash.Query.Aggregate.kind_to_type(aggregate.kind, attribute_type, attribute_constraints) + + type + |> Ash.Resource.Info.aggregate_type(aggregate) + |> split_at_paths(constraints, right) + |> Enum.reduce(do_dynamic_expr(query, left, bindings, embedded?, acc), fn data, {expr, acc} -> + do_get_path(query, expr, data, bindings, embedded?, pred_embedded?, acc) + end) + end + defp do_dynamic_expr( query, %GetPath{ From 89a4363c1f40404daae31ccaed23c230dff88d7e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 23 Dec 2023 10:39:54 -0500 Subject: [PATCH 0139/1215] improvement: support directly referencing aggregates from aggregates --- lib/aggregate.ex | 98 +++++++++++++++++++----------- lib/expr.ex | 40 ++++++++++++ mix.lock | 8 +-- test/complex_calculations_test.exs | 54 ++++++++++++++++ test/support/resources/author.ex | 1 + test/support/resources/post.ex | 8 +++ 6 files changed, 171 insertions(+), 38 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 7e3170ea..b5324d6d 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -79,7 +79,13 @@ defmodule AshPostgres.Aggregate do cond do is_single? && optimizable_first_aggregate?(resource, Enum.at(aggregates, 0)) -> - case add_first_join_aggregate(query, resource, hd(aggregates), root_data) do + case add_first_join_aggregate( + query, + resource, + hd(aggregates), + root_data, + first_relationship + ) do {:ok, query, dynamic} -> query = if select? do @@ -277,6 +283,7 @@ defmodule AshPostgres.Aggregate do end |> case do {:ok, aggregate} -> + aggregate = Map.put(aggregate, :load, aggregate.name) {:cont, {:ok, [aggregate | aggregates]}} {:error, error} -> @@ -285,7 +292,7 @@ defmodule AshPostgres.Aggregate do end) end - defp add_first_join_aggregate(query, resource, aggregate, root_data) do + defp add_first_join_aggregate(query, resource, aggregate, root_data, first_relationship) do {resource, path} = case root_data do {resource, path} -> @@ -312,20 +319,13 @@ defmodule AshPostgres.Aggregate do ) do {:ok, query} -> ref = - %Ash.Query.Ref{ - attribute: - aggregate_field( - aggregate, - Ash.Resource.Info.related( - resource, - path ++ aggregate.relationship_path - ), - path ++ aggregate.relationship_path, - query - ), - relationship_path: path ++ aggregate.relationship_path, - resource: resource - } + aggregate_field_ref( + aggregate, + Ash.Resource.Info.related(resource, path ++ aggregate.relationship_path), + path ++ aggregate.relationship_path, + query, + first_relationship + ) {value, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) @@ -390,8 +390,14 @@ defmodule AshPostgres.Aggregate do agg_query = case Ash.Resource.Info.field(related, aggregate.field) do - %Ash.Resource.Aggregate{} -> - raise "can't do this yet" + %Ash.Resource.Aggregate{} = aggregate -> + {:ok, agg_query} = + add_aggregates(agg_query, [aggregate], related, false, 0, { + first_relationship.destination, + [first_relationship.name] + }) + + agg_query %Ash.Resource.Calculation{ name: name, @@ -826,11 +832,14 @@ defmodule AshPostgres.Aggregate do ) do query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) - ref = %Ash.Query.Ref{ - attribute: aggregate_field(aggregate, resource, relationship_path, query), - relationship_path: relationship_path, - resource: query.__ash_bindings__.resource - } + ref = + aggregate_field_ref( + aggregate, + resource, + relationship_path, + query, + first_relationship + ) type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) @@ -937,11 +946,14 @@ defmodule AshPostgres.Aggregate do [:left, :inner, :root] ) - ref = %Ash.Query.Ref{ - attribute: aggregate_field(aggregate, resource, relationship_path, query), - relationship_path: relationship_path, - resource: query.__ash_bindings__.resource - } + ref = + aggregate_field_ref( + aggregate, + resource, + relationship_path, + query, + first_relationship + ) {field, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) @@ -1037,16 +1049,19 @@ defmodule AshPostgres.Aggregate do %{kind: kind} = aggregate, resource, is_single?, - _first_relationship + first_relationship ) when kind in [:count, :sum, :avg, :max, :min, :custom] do query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) - ref = %Ash.Query.Ref{ - attribute: aggregate_field(aggregate, resource, relationship_path, query), - relationship_path: relationship_path, - resource: resource - } + ref = + aggregate_field_ref( + aggregate, + resource, + relationship_path, + query, + first_relationship + ) {field, query} = if kind == :custom do @@ -1171,6 +1186,21 @@ defmodule AshPostgres.Aggregate do Ecto.Query.select_merge(query, ^%{aggregate_name => casted}) end + def aggregate_field_ref(aggregate, resource, relationship_path, query, first_relationship) do + %Ash.Query.Ref{ + attribute: aggregate_field(aggregate, resource, relationship_path, query), + relationship_path: relationship_path, + resource: query.__ash_bindings__.resource + } + |> case do + %{attribute: %Ash.Resource.Aggregate{}} = ref -> + %{ref | relationship_path: [first_relationship.name | ref.relationship_path]} + + other -> + other + end + end + defp single_path?(_, []), do: true defp single_path?(resource, [relationship | rest]) do diff --git a/lib/expr.ex b/lib/expr.ex index 2d8ea867..2fa1f4ce 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1562,6 +1562,30 @@ defmodule AshPostgres.Expr do {expr, acc} end + defp do_dynamic_expr( + query, + %Ref{attribute: %Ash.Resource.Aggregate{name: name}} = ref, + bindings, + _embedded?, + acc, + _expr_type + ) do + ref_binding = ref_binding(ref, bindings) + + if is_nil(ref_binding) do + raise "Error while building reference: #{inspect(ref)}" + end + + expr = + if query.__ash_bindings__[:parent?] do + Ecto.Query.dynamic(field(parent_as(^ref_binding), ^name)) + else + Ecto.Query.dynamic(field(as(^ref_binding), ^name)) + end + + {expr, acc} + end + defp do_dynamic_expr(_query, %Ash.Vector{} = value, _bindings, _embedded?, acc, _type) do {value, acc} end @@ -1957,6 +1981,22 @@ defmodule AshPostgres.Expr do end) end + defp ref_binding( + %{ + attribute: %Ash.Resource.Aggregate{name: name}, + relationship_path: relationship_path + }, + bindings + ) do + IO.inspect("HERE") + + Enum.find_value(bindings.bindings, fn {binding, data} -> + data.type == :aggregate && + data.path == relationship_path && + Enum.any?(data.aggregates, &(&1.name == name)) && binding + end) + end + defp ref_binding(%{attribute: %Ash.Resource.Attribute{}} = ref, bindings) do Enum.find_value(bindings.bindings, fn {binding, data} -> data.type in [:inner, :left, :root] && diff --git a/mix.lock b/mix.lock index ed2d82f1..be2c7318 100644 --- a/mix.lock +++ b/mix.lock @@ -5,14 +5,14 @@ "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"}, - "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, - "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, + "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -34,7 +34,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, - "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, "spark": {:hex, :spark, "1.1.53", "db8a374ef6ada4f38389386bec76b2fa6331d4755308a6e359acad16472e29ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "5f8a8e2b4abd2544517bb8d29c28576239254b5979d66d9781b154706c4199dd"}, diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index aae11a6f..829f3a58 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -222,4 +222,58 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do |> Ash.Query.filter(author_count_of_posts == 1) |> AshPostgres.Test.Api.read!() end + + test "calculations can reference aggregates from optimizable first aggregates" do + author = + AshPostgres.Test.Author + |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) + |> AshPostgres.Test.Api.create!() + + AshPostgres.Test.Post + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> AshPostgres.Test.Api.create!() + + assert [%{author_count_of_posts_agg: 1}] = + AshPostgres.Test.Post + |> Ash.Query.load(:author_count_of_posts_agg) + |> AshPostgres.Test.Api.read!() + + assert [%{author_count_of_posts_agg: 1}] = + AshPostgres.Test.Post + |> AshPostgres.Test.Api.read!() + |> AshPostgres.Test.Api.load!(:author_count_of_posts_agg) + + assert [_] = + AshPostgres.Test.Post + |> Ash.Query.filter(author_count_of_posts_agg == 1) + |> AshPostgres.Test.Api.read!() + end + + test "calculations can reference aggregates from non optimizable aggregates" do + author = + AshPostgres.Test.Author + |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) + |> AshPostgres.Test.Api.create!() + + AshPostgres.Test.Post + |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> AshPostgres.Test.Api.create!() + + assert [%{sum_of_author_count_of_posts: 1}] = + AshPostgres.Test.Post + |> Ash.Query.load(:sum_of_author_count_of_posts) + |> AshPostgres.Test.Api.read!() + + assert [%{sum_of_author_count_of_posts: 1}] = + AshPostgres.Test.Post + |> AshPostgres.Test.Api.read!() + |> AshPostgres.Test.Api.load!(:sum_of_author_count_of_posts) + + assert [_] = + AshPostgres.Test.Post + |> Ash.Query.filter(sum_of_author_count_of_posts == 1) + |> AshPostgres.Test.Api.read!() + end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index f84aa5fc..82ef7b17 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -27,6 +27,7 @@ defmodule AshPostgres.Test.Author do aggregates do first(:profile_description, :profile, :description) + count(:count_of_posts, :posts) end calculations do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 2bc3f929..2774a914 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -244,6 +244,14 @@ defmodule AshPostgres.Test.Post do calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) + calculate( + :sum_of_author_count_of_posts, + :integer, + expr(sum(author, field: :count_of_posts)) + ) + + calculate(:author_count_of_posts_agg, :integer, expr(author.count_of_posts)) + calculate( :price_string, :string, From 6ff25bf342746d17215c74d89206dc988e15eade Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Dec 2023 10:08:13 -0500 Subject: [PATCH 0140/1215] improvement: require `name` when generating migrations --- .../tasks/ash_postgres.generate_migrations.ex | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 3b6249df..4a3154d1 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -72,6 +72,18 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do @shortdoc "Generates migrations, and stores a snapshot of your resources" def run(args) do + {name, args} = + case args do + ["-" <> _ | _] -> + {nil, args} + + [first | rest] -> + {first, rest} + + [] -> + {nil, []} + end + {opts, _} = OptionParser.parse!(args, strict: [ @@ -94,6 +106,18 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do opts |> Keyword.put(:format, !opts[:no_format]) |> Keyword.delete(:no_format) + |> Keyword.put_new(:name, name) + + if !opts[:name] && !opts[:dry_run] && !opts[:check] do + IO.warn(""" + Name must be provided when generating migrations, unless `--dry-run` or `--check` is also provided. + Using an autogenerated name will be deprecated in a future release. + + Please provide a name. for example: + + mix ash_postgres.generate_migrations #{Enum.join(args, " ")} + """) + end AshPostgres.MigrationGenerator.generate(apis, opts) end From 9d6c17f2c2be08584e1af609c6ebe94963940176 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Dec 2023 10:21:57 -0500 Subject: [PATCH 0141/1215] chore: remove inspect --- lib/expr.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 2fa1f4ce..9f1f64ea 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1988,8 +1988,6 @@ defmodule AshPostgres.Expr do }, bindings ) do - IO.inspect("HERE") - Enum.find_value(bindings.bindings, fn {binding, data} -> data.type == :aggregate && data.path == relationship_path && From 40df585d62224a9cee588d5112f51cf69a39bc7b Mon Sep 17 00:00:00 2001 From: mrdotb Date: Wed, 27 Dec 2023 22:51:48 +0100 Subject: [PATCH 0142/1215] docs: Add more fragment examples (#188) --- documentation/how_to/using-fragments.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/documentation/how_to/using-fragments.md b/documentation/how_to/using-fragments.md index c6214b80..5029a91a 100644 --- a/documentation/how_to/using-fragments.md +++ b/documentation/how_to/using-fragments.md @@ -23,3 +23,21 @@ fragment("points > (SELECT SUM(points) FROM games WHERE user_id = ? AND id != ?) ``` Using entire queries like the above is a last resort, but can often help us avoid having to add extra structure unnecessarily. + +sql function in a calculate + +```elixir +calculations do + calculate :lower_name, :string, expr( + fragment("LOWER(?)", name) + ) +end +``` + +sql function in a migration + +```elixir +create table(:managers, primary_key: false) do + add :id, :uuid, null: false, default: fragment("UUID_GENERATE_V4()"), primary_key: true +end +``` From 2a2fd30e33a6d4e9fc40fd62fa47f7656ee1bf52 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 29 Dec 2023 21:49:34 -0500 Subject: [PATCH 0143/1215] improvement: support new `return_query/2` callback improvement: support new `:no_rollback` error signal --- lib/data_layer.ex | 145 +++++++++++++++++++++------------------ lib/repo.ex | 7 ++ mix.exs | 2 +- mix.lock | 2 +- test/error_expr_test.exs | 2 +- 5 files changed, 88 insertions(+), 70 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 84dfba14..0d5728db 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -586,11 +586,16 @@ defmodule AshPostgres.DataLayer do |> Map.put(:parent_resources, [ parent.__ash_bindings__.resource | parent.__ash_bindings__[:parent_resources] || [] ]) + |> Map.put(:lateral_join?, true) {:ok, %{data_layer_query | __ash_bindings__: ash_bindings}} _ -> - {:ok, data_layer_query} + ash_bindings = + data_layer_query.__ash_bindings__ + |> Map.put(:lateral_join?, false) + + {:ok, %{data_layer_query | __ash_bindings__: ash_bindings}} end end @@ -606,7 +611,17 @@ defmodule AshPostgres.DataLayer do end @impl true - def run_query(query, resource) do + def return_query(%{__ash_bindings__: %{lateral_join?: true}} = query, resource) do + query = default_bindings(query, resource) + + if query.__ash_bindings__[:sort_applied?] do + {:ok, query} + else + apply_sort(query, query.__ash_bindings__[:sort], query.__ash_bindings__.resource) + end + end + + def return_query(query, resource) do query = default_bindings(query, resource) with_sort_applied = @@ -621,46 +636,50 @@ defmodule AshPostgres.DataLayer do {:error, error} {:ok, query} -> - query = - if query.__ash_bindings__[:__order__?] && query.windows[:order] do - if query.distinct do - query_with_order = - from(row in query, select_merge: %{__order__: over(row_number(), :order)}) - - query_without_limit_and_offset = - query_with_order - |> Ecto.Query.exclude(:limit) - |> Ecto.Query.exclude(:offset) - - from(row in subquery(query_without_limit_and_offset), - select: row, - order_by: row.__order__ - ) - |> Map.put(:limit, query.limit) - |> Map.put(:offset, query.offset) - else - order_by = %{query.windows[:order] | expr: query.windows[:order].expr[:order_by]} + if query.__ash_bindings__[:__order__?] && query.windows[:order] do + if query.distinct do + query_with_order = + from(row in query, select_merge: %{__order__: over(row_number(), :order)}) - %{ - query - | windows: Keyword.delete(query.windows, :order), - order_bys: [order_by] - } - end + query_without_limit_and_offset = + query_with_order + |> Ecto.Query.exclude(:limit) + |> Ecto.Query.exclude(:offset) + + {:ok, + from(row in subquery(query_without_limit_and_offset), + select: row, + order_by: row.__order__ + ) + |> Map.put(:limit, query.limit) + |> Map.put(:offset, query.offset)} else - %{query | windows: Keyword.delete(query.windows, :order)} - end + order_by = %{query.windows[:order] | expr: query.windows[:order].expr[:order_by]} - if AshPostgres.DataLayer.Info.polymorphic?(resource) && no_table?(query) do - raise_table_error!(resource, :read) + {:ok, + %{ + query + | windows: Keyword.delete(query.windows, :order), + order_bys: [order_by] + }} + end else - repo = dynamic_repo(resource, query) - - with_savepoint(repo, query, fn -> - {:ok, repo.all(query, repo_opts(nil, nil, resource))} - end) + {:ok, %{query | windows: Keyword.delete(query.windows, :order)}} end end + end + + @impl true + def run_query(query, resource) do + if AshPostgres.DataLayer.Info.polymorphic?(resource) && no_table?(query) do + raise_table_error!(resource, :read) + else + repo = dynamic_repo(resource, query) + + with_savepoint(repo, query, fn -> + {:ok, repo.all(query, repo_opts(nil, nil, resource))} + end) + end rescue e -> handle_raised_error(e, __STACKTRACE__, query, resource) @@ -888,39 +907,26 @@ defmodule AshPostgres.DataLayer do _destination_resource, path ) do - with_sort_applied = - if query.__ash_bindings__[:sort_applied?] do - {:ok, query} - else - apply_sort(query, query.__ash_bindings__[:sort], query.__ash_bindings__.resource) - end - - case with_sort_applied do - {:error, error} -> - {:error, error} - + case lateral_join_query( + query, + root_data, + path + ) do {:ok, query} -> - case lateral_join_query( - query, - root_data, - path - ) do - {:ok, query} -> - source_resource = - path - |> Enum.at(0) - |> elem(0) - |> Map.get(:resource) + source_resource = + path + |> Enum.at(0) + |> elem(0) + |> Map.get(:resource) - {:ok, - dynamic_repo(source_resource, query).all( - query, - repo_opts(nil, nil, source_resource) - )} + {:ok, + dynamic_repo(source_resource, query).all( + query, + repo_opts(nil, nil, source_resource) + )} - {:error, error} -> - {:error, error} - end + {:error, error} -> + {:error, error} end end @@ -2326,7 +2332,12 @@ defmodule AshPostgres.DataLayer do @impl true def sort(query, sort, _resource) do - {:ok, Map.update!(query, :__ash_bindings__, &Map.put(&1, :sort, sort))} + {:ok, + Map.update!( + query, + :__ash_bindings__, + &Map.put(&1, :sort, sort) + )} end @impl true diff --git a/lib/repo.ex b/lib/repo.ex index 75517622..3cd42c71 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -90,6 +90,13 @@ defmodule AshPostgres.Repo do def override_migration_type(type), do: type def min_pg_version, do: 10 + def transaction!(fun) do + case fun.() do + {:ok, value} -> value + {:error, error} -> raise Ash.Error.to_error_class(error) + end + end + def all_tenants do raise """ `#{inspect(__MODULE__)}.all_tenants/0` was called, but was not defined. In order to migrate tenants, you must define this function. diff --git a/mix.exs b/mix.exs index 0178cc50..fb388eee 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.17")}, + {:ash, ash_version("~> 2.17 and >= 2.17.19")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index be2c7318..fa57988c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.17", "437688358c4f3fe18087e47b16388f0cc6c1eaaadfe44c0eeef1ef7c871ed9f4", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df074e4246db04351344db23f36598535d568f7a91023eafd7af698e34804f0b"}, + "ash": {:hex, :ash, "2.17.19", "6cae99caf0e17c06780c2e9ec4553ee8799593e3c13d072be8199724a3c00922", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f26f974e0e31e0bb4ae6923c9f24386f8ba2dec2e4657bb774d32bc265ad366c"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/error_expr_test.exs b/test/error_expr_test.exs index 201c70d6..9e9b565b 100644 --- a/test/error_expr_test.exs +++ b/test/error_expr_test.exs @@ -42,7 +42,7 @@ defmodule AshPostgres.ErrorExprTest do |> Api.create!() assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> - AshPostgres.TestRepo.transaction(fn -> + AshPostgres.TestRepo.transaction!(fn -> Post |> Ash.Query.calculate( :test, From 4e12c10297812bf53f4b2e308fa8dadf92331b87 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 29 Dec 2023 22:14:25 -0500 Subject: [PATCH 0144/1215] chore: release version v1.3.66 --- CHANGELOG.md | 17 +++++++++++++++++ mix.exs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466589dd..6541b322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.66](https://github.com/ash-project/ash_postgres/compare/v1.3.65...v1.3.66) (2023-12-30) + + + + +### Improvements: + +* support new `return_query/2` callback + +* support new `:no_rollback` error signal + +* require `name` when generating migrations + +* support directly referencing aggregates from aggregates + +* support aggregates as `get_path` subject + ## [v1.3.65](https://github.com/ash-project/ash_postgres/compare/v1.3.64...v1.3.65) (2023-12-23) diff --git a/mix.exs b/mix.exs index fb388eee..c91ff680 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.65" + @version "1.3.66" def project do [ From c0c962153419cd69ca839fdcba6c4131c687ff9e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 31 Dec 2023 08:40:30 -0500 Subject: [PATCH 0145/1215] improvement: support new bulk operations fix: support encoding errors with expressions in them --- lib/data_layer.ex | 202 +++++++++++++++--- lib/expr.ex | 48 ++++- lib/migration_generator/ash_functions.ex | 23 +- mix.exs | 3 +- mix.lock | 2 +- priv/resource_snapshots/extensions.json | 2 +- ...1611_install_ash-functions_extension_3.exs | 63 ++++++ test/bulk_destroy_test.exs | 30 +++ test/bulk_update_test.exs | 38 ++++ test_snapshot_path/extensions.json | 2 +- 10 files changed, 375 insertions(+), 38 deletions(-) create mode 100644 priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs create mode 100644 test/bulk_destroy_test.exs create mode 100644 test/bulk_update_test.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 0d5728db..3e266b34 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -431,6 +431,15 @@ defmodule AshPostgres.DataLayer do @impl true def can?(_, :async_engine), do: true def can?(_, :bulk_create), do: true + + def can?(resource, :update_query) do + # We can't currently support updating a record from a query + # if that record manages a tenant on update + !AshPostgres.DataLayer.Info.manage_tenant_update?(resource) + end + + def can?(_, :destroy_query), do: true + def can?(_, {:lock, :for_update}), do: true def can?(_, :composite_types), do: true @@ -1208,6 +1217,117 @@ defmodule AshPostgres.DataLayer do from(row in {AshPostgres.DataLayer.Info.table(resource) || "", resource}, []) end + @impl true + def update_query(query, changeset, resource, options) do + ecto_changeset = + changeset.data + |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) + |> ecto_changeset(changeset, :update, true, true) + + try do + query = + query + |> default_bindings(resource, changeset.context) + + query = + if options[:return_records?] do + attrs = resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) + + Ecto.Query.select(query, ^attrs) + else + query + end + + case query_with_atomics( + resource, + query, + ecto_changeset.filters, + changeset.atomics, + ecto_changeset.changes, + [] + ) do + :empty -> + {:ok, changeset.data} + + {:ok, query} -> + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + + repo_opts = + Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + + repo = dynamic_repo(resource, changeset) + + {_, results} = + with_savepoint(repo, query, fn -> + repo.update_all( + query, + [], + repo_opts + ) + end) + + if options[:return_records?] do + {:ok, results} + else + :ok + end + + {:error, error} -> + {:error, error} + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + end + end + + @impl true + def destroy_query(query, changeset, resource, options) do + ecto_changeset = + changeset.data + |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) + |> ecto_changeset(changeset, :update, true, true) + + try do + query = + query + |> default_bindings(resource, changeset.context) + + query = + if options[:return_records?] do + attrs = resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) + + Ecto.Query.select(query, ^attrs) + else + query + end + + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + + repo_opts = + Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + + repo = dynamic_repo(resource, changeset) + + {_, results} = + with_savepoint(repo, query, fn -> + repo.delete_all( + query, + repo_opts + ) + end) + + if options[:return_records?] do + {:ok, results} + else + :ok + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + end + end + @impl true def bulk_create(resource, stream, options) do opts = repo_opts(nil, options[:tenant], resource) @@ -1512,7 +1632,7 @@ defmodule AshPostgres.DataLayer do ) end - defp ecto_changeset(record, changeset, type, table_error? \\ true) do + defp ecto_changeset(record, changeset, type, table_error? \\ true, bulk_update? \\ false) do filters = if changeset.action_type == :create do %{} @@ -1521,7 +1641,7 @@ defmodule AshPostgres.DataLayer do end filters = - if changeset.action_type == :create do + if changeset.action_type == :create || bulk_update? do filters else changeset.resource @@ -1632,6 +1752,49 @@ defmodule AshPostgres.DataLayer do ) end + defp handle_raised_error( + %Postgrex.Error{ + postgres: %{ + code: :raise_exception, + message: "ash_error: \"" <> json, + severity: "ERROR" + } + }, + _, + _, + _ + ) do + %{"exception" => exception, "input" => input} = + json + |> String.trim_trailing("\"") + |> String.replace("\\\"", "\"") + |> Jason.decode!() + + exception = Module.concat([exception]) + + {:error, Ash.Error.from_json(exception, input)} + end + + defp handle_raised_error( + %Postgrex.Error{ + postgres: %{ + code: :raise_exception, + message: "ash_error: " <> json, + severity: "ERROR" + } + }, + _, + _, + _ + ) do + %{"exception" => exception, "input" => input} = + Jason.decode!(json) + + exception = Module.concat([exception]) + + {:error, Ash.Error.from_json(exception, input)} + end + defp handle_raised_error( %Postgrex.Error{} = error, stacktrace, @@ -1667,29 +1830,6 @@ defmodule AshPostgres.DataLayer do end end - defp handle_raised_error( - %Postgrex.Error{ - postgres: %{ - code: :raise_exception, - message: "\"ash_exception: " <> json, - severity: "ERROR" - } - }, - _, - _, - _ - ) do - %{"exception" => exception, "input" => input} = - json - |> String.trim_trailing("\"") - |> String.replace("\\\"", "\"") - |> Jason.decode!() - - exception = Module.concat([exception]) - - {:error, Ash.Error.from_json(exception, input)} - end - defp handle_raised_error(error, stacktrace, _ecto_changeset, _resource) do {:error, Ash.Error.to_ash_error(error, stacktrace)} end @@ -2186,6 +2326,10 @@ defmodule AshPostgres.DataLayer do used_aggregates = Ash.Filter.used_aggregates(expr, []) + attribute = Ash.Resource.Info.attribute(resource, field) + + type = AshPostgres.Types.parameterized_type(attribute.type, attribute.constraints) + with {:ok, query} <- AshPostgres.Join.join_all_relationships( query, @@ -2198,7 +2342,13 @@ defmodule AshPostgres.DataLayer do {:ok, query} <- AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0), {dynamic, acc} <- - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__) do + AshPostgres.Expr.dynamic_expr( + query, + expr, + query.__ash_bindings__, + false, + type + ) do {:cont, {:ok, merge_expr_accumulator(query, acc), Keyword.put(set, field, dynamic)}} else other -> diff --git a/lib/expr.ex b/lib/expr.ex index 9f1f64ea..90bf0f72 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1295,8 +1295,8 @@ defmodule AshPostgres.Expr do defp do_dynamic_expr( query, %Error{arguments: [exception, input]} = value, - _bindings, - _embedded?, + bindings, + embedded?, acc, type ) do @@ -1308,8 +1308,48 @@ defmodule AshPostgres.Expr do raise "Input expression to `error` must be a map or keyword list" end - encoded = - "ash_exception: " <> Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}) + {encoded, acc} = + if Ash.Filter.TemplateHelpers.expr?(input) do + frag_parts = + Enum.map(input, fn {key, value} -> + if Ash.Filter.TemplateHelpers.expr?(value) do + [ + expr: to_string(key), + raw: "::text, ", + expr: value + ] + else + [ + expr: to_string(key), + raw: "::text, ", + expr: value, + raw: "::jsonb" + ] + end + end) + |> Enum.intersperse(raw: ", ") + |> List.flatten() + + do_dynamic_expr( + query, + %Fragment{ + embedded?: false, + arguments: + [ + raw: "jsonb_build_object('exception', ", + expr: inspect(exception), + raw: "::text, 'input', jsonb_build_object(" + ] ++ + frag_parts ++ + [raw: "))"] + }, + bindings, + embedded?, + acc + ) + else + {Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}), acc} + end if type do # This is a type hint, if we're raising an error, we tell it what the value diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 65f29f4b..5b91a3c7 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do - @latest_version 2 + @latest_version 3 def latest_version, do: @latest_version @@ -120,6 +120,14 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do ash_raise_error() end + def install(2) do + ash_raise_error() + end + + def drop(2) do + ash_raise_error(false) + end + def drop(1) do "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)\")" end @@ -132,7 +140,14 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE) ash_trim_whitespace(text[])\")" end - defp ash_raise_error do + defp ash_raise_error(prefix? \\ true) do + prefix = + if prefix? do + "ash_error: " + else + "" + end + """ execute(\"\"\" CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) @@ -140,7 +155,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do BEGIN -- Raise an error with the provided JSON data. -- The JSON object is converted to text for inclusion in the error message. - RAISE EXCEPTION '%', json_data::text; + RAISE EXCEPTION '#{prefix}%', json_data::text; RETURN NULL; END; $$ LANGUAGE plpgsql; @@ -152,7 +167,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do BEGIN -- Raise an error with the provided JSON data. -- The JSON object is converted to text for inclusion in the error message. - RAISE EXCEPTION '%', json_data::text; + RAISE EXCEPTION '#{prefix}%', json_data::text; RETURN NULL; END; $$ LANGUAGE plpgsql; diff --git a/mix.exs b/mix.exs index c91ff680..93fd1dff 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,8 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.19")}, + {:ash, + ash_version(github: "ash-project/ash", ref: "fe2156a9ac9e49cae44132552ce6e52aee14104c")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index fa57988c..d0547b22 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.19", "6cae99caf0e17c06780c2e9ec4553ee8799593e3c13d072be8199724a3c00922", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f26f974e0e31e0bb4ae6923c9f24386f8ba2dec2e4657bb774d32bc265ad366c"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "fe2156a9ac9e49cae44132552ce6e52aee14104c", [ref: "fe2156a9ac9e49cae44132552ce6e52aee14104c"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/priv/resource_snapshots/extensions.json b/priv/resource_snapshots/extensions.json index d134b4d9..e084bbff 100644 --- a/priv/resource_snapshots/extensions.json +++ b/priv/resource_snapshots/extensions.json @@ -6,5 +6,5 @@ "citext", "demo-functions_v1" ], - "ash_functions_version": 2 + "ash_functions_version": 3 } \ No newline at end of file diff --git a/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs b/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs new file mode 100644 index 00000000..2417e5d7 --- /dev/null +++ b/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs @@ -0,0 +1,63 @@ +defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension3 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION '%', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION '%', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + end +end \ No newline at end of file diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs new file mode 100644 index 00000000..8d0e0c6c --- /dev/null +++ b/test/bulk_destroy_test.exs @@ -0,0 +1,30 @@ +defmodule AshPostgres.BulkDestroyTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.{Api, Post} + + require Ash.Expr + require Ash.Query + + test "bulk destroys can run with nothing in the table" do + Api.bulk_destroy!(Post, :update, %{title: "new_title"}) + end + + test "bulk destroys destroy everything pertaining to the query" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Api.bulk_destroy!(Post, :update, %{}) + + assert Api.read!(Post) == [] + end + + test "bulk updates only apply to things that the query produces" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.Query.filter(title == "fred") + |> Api.bulk_destroy!(:update, %{}) + + # 😢 sad + assert [%{title: "george"}] = Api.read!(Post) + end +end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs new file mode 100644 index 00000000..200c8f97 --- /dev/null +++ b/test/bulk_update_test.exs @@ -0,0 +1,38 @@ +defmodule AshPostgres.BulkUpdateTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.{Api, Post} + + require Ash.Expr + require Ash.Query + + test "bulk updates can run with nothing in the table" do + Api.bulk_update!(Post, :update, %{title: "new_title"}) + end + + test "bulk updates update everything pertaining to the query" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Api.bulk_update!(Post, :update, %{}, + atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")} + ) + + posts = Api.read!(Post) + assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff")) + end + + test "bulk updates only apply to things that the query produces" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.Query.filter(title == "fred") + |> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) + + titles = + Post + |> Api.read!() + |> Enum.map(& &1.title) + |> Enum.sort() + + assert titles == ["fred_stuff", "george"] + end +end diff --git a/test_snapshot_path/extensions.json b/test_snapshot_path/extensions.json index d134b4d9..e084bbff 100644 --- a/test_snapshot_path/extensions.json +++ b/test_snapshot_path/extensions.json @@ -6,5 +6,5 @@ "citext", "demo-functions_v1" ], - "ash_functions_version": 2 + "ash_functions_version": 3 } \ No newline at end of file From 3cb33723fe82f04c51b7716204331a874e32d042 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Jan 2024 01:09:16 -0500 Subject: [PATCH 0146/1215] improvement: support latest ash version & operator overrides --- lib/types/types.ex | 6 ++++++ mix.exs | 3 +-- mix.lock | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/types/types.ex b/lib/types/types.ex index 04e4306c..673700e7 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -78,6 +78,11 @@ defmodule AshPostgres.Types do def determine_types(mod, values) do Code.ensure_compiled(mod) + name = + if function_exported?(mod, :name, 0) do + mod.name + end + cond do :erlang.function_exported(mod, :types, 0) -> mod.types() @@ -88,6 +93,7 @@ defmodule AshPostgres.Types do true -> [:any] end + |> Enum.concat(Ash.Query.Operator.operator_overloads(name)) |> Enum.map(fn types -> case types do :same -> diff --git a/mix.exs b/mix.exs index 93fd1dff..f69929a1 100644 --- a/mix.exs +++ b/mix.exs @@ -204,8 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, - ash_version(github: "ash-project/ash", ref: "fe2156a9ac9e49cae44132552ce6e52aee14104c")}, + {:ash, ash_version("~> 2.17 and >= 2.17.20")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index d0547b22..1c1a780d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "fe2156a9ac9e49cae44132552ce6e52aee14104c", [ref: "fe2156a9ac9e49cae44132552ce6e52aee14104c"]}, + "ash": {:hex, :ash, "2.17.20", "8b201335fac2f9ec8eb89c71c7c9007d11a09089dd82aa070ed4214c7ae02400", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c89da37cf7464803b09cdd6f20c0b944764ea124b782cdfc72eeb9ac43a11445"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From 80af052cf586f2da2841dc23abeaa9d70ce2e546 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Jan 2024 01:09:40 -0500 Subject: [PATCH 0147/1215] chore: release version v1.3.67 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6541b322..3c5bbf8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.67](https://github.com/ash-project/ash_postgres/compare/v1.3.66...v1.3.67) (2024-01-04) + + + + +### Bug Fixes: + +* support encoding errors with expressions in them + +### Improvements: + +* support latest ash version & operator overrides + +* support new bulk operations + ## [v1.3.66](https://github.com/ash-project/ash_postgres/compare/v1.3.65...v1.3.66) (2023-12-30) diff --git a/mix.exs b/mix.exs index f69929a1..807f6447 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.66" + @version "1.3.67" def project do [ From d08c2614d77dc4cf7086bb8215b0e7b48693d508 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Jan 2024 01:12:28 -0500 Subject: [PATCH 0148/1215] chore: update ash version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 807f6447..f69929a1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.67" + @version "1.3.66" def project do [ From d946971e0db9a5060c7a7d6b784064781d535b41 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Jan 2024 01:14:45 -0500 Subject: [PATCH 0149/1215] chore: update version in mix.exs --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index f69929a1..807f6447 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.66" + @version "1.3.67" def project do [ From f1d5d483a6713fa00a1f1808101b5c08a89807d8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Jan 2024 14:08:05 -0500 Subject: [PATCH 0150/1215] fix: properly gather types for operator & function overloads --- lib/types/types.ex | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/types/types.ex b/lib/types/types.ex index 673700e7..bb9fa7b3 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -79,8 +79,15 @@ defmodule AshPostgres.Types do Code.ensure_compiled(mod) name = - if function_exported?(mod, :name, 0) do - mod.name + cond do + function_exported?(mod, :operator, 0) -> + mod.operator() + + function_exported?(mod, :name, 0) -> + mod.name() + + true -> + nil end cond do @@ -93,7 +100,7 @@ defmodule AshPostgres.Types do true -> [:any] end - |> Enum.concat(Ash.Query.Operator.operator_overloads(name)) + |> Enum.concat(Map.keys(Ash.Query.Operator.operator_overloads(name) || %{})) |> Enum.map(fn types -> case types do :same -> From 6b24a7809a81ab1bec4da6a22a6c55f0eb0190a1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Jan 2024 14:11:52 -0500 Subject: [PATCH 0151/1215] chore: release version v1.3.68 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c5bbf8c..1efa352c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.3.68](https://github.com/ash-project/ash_postgres/compare/v1.3.67...v1.3.68) (2024-01-04) + + + + +### Bug Fixes: + +* properly gather types for operator & function overloads + ## [v1.3.67](https://github.com/ash-project/ash_postgres/compare/v1.3.66...v1.3.67) (2024-01-04) diff --git a/mix.exs b/mix.exs index 807f6447..15c11cb4 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.67" + @version "1.3.68" def project do [ From 65180ec3acc6d2559a3ca78b605db659054b1eac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 5 Jan 2024 10:01:40 -0500 Subject: [PATCH 0152/1215] fix: honor configured schema on bulk create --- lib/data_layer.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3e266b34..c4d8661c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1400,6 +1400,13 @@ defmodule AshPostgres.DataLayer do resource end + opts = + if schema = Enum.at(changesets, 0).context[:data_layer][:schema] do + Keyword.put(opts, :prefix, schema) + else + opts + end + result = with_savepoint(repo, opts[:on_conflict], fn -> repo.insert_all(source, ecto_changesets, opts) From a60dbf172551049b7958b9eef0e305438db4e2de Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 8 Jan 2024 08:39:13 -0500 Subject: [PATCH 0153/1215] fix: properly configure `polymorphic_name` option --- lib/data_layer.ex | 9 ++------- lib/data_layer/info.ex | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c4d8661c..3863dbdf 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -164,9 +164,9 @@ defmodule AshPostgres.DataLayer do "For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables." ], polymorphic_name: [ - type: {:one_of, [:update, :nilify, :nothing, :restrict]}, + type: :string, doc: - "For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables." + "For polymorphic resources, then index name to use for the foreign key to the source table." ] ] } @@ -235,11 +235,6 @@ defmodule AshPostgres.DataLayer do type: {:one_of, [:update, :nilify, :nothing, :restrict]}, doc: "For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables." - ], - polymorphic_name: [ - type: {:one_of, [:update, :nilify, :nothing, :restrict]}, - doc: - "For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables." ] ] } diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 1e84574b..3184660b 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -82,7 +82,7 @@ defmodule AshPostgres.DataLayer.Info do @doc "The configured polymorphic_reference_name for a resource" def polymorphic_name(resource) do - Extension.get_opt(resource, [:postgres, :references], :polymorphic_on_delete, nil, true) + Extension.get_opt(resource, [:postgres, :references], :polymorphic_name, nil, true) end @doc "The configured polymorphic? for a resource" From 011330df7eaeee3f0167374739d43ada2611b68b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 8 Jan 2024 08:55:05 -0500 Subject: [PATCH 0154/1215] chore: fix cheat sheets --- documentation/dsls/DSL:-AshPostgres.DataLayer.md | 1 - mix.exs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 2262d16c..af2b1853 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -257,7 +257,6 @@ end |------|------|---------|------| | [`polymorphic_on_delete`](#postgres-references-polymorphic_on_delete){: #postgres-references-polymorphic_on_delete } | `:delete \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | | [`polymorphic_on_update`](#postgres-references-polymorphic_on_update){: #postgres-references-polymorphic_on_update } | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | -| [`polymorphic_name`](#postgres-references-polymorphic_name){: #postgres-references-polymorphic_name } | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | diff --git a/mix.exs b/mix.exs index 15c11cb4..b1e88c24 100644 --- a/mix.exs +++ b/mix.exs @@ -241,7 +241,7 @@ defmodule AshPostgres.MixProject do "sobelow --skip -i Config.Secrets --ignore-files lib/migration_generator/migration_generator.ex", credo: "credo --strict", docs: [ - # "spark.cheat_sheets", + "spark.cheat_sheets", "docs", "spark.replace_doc_links", "spark.cheat_sheets_in_search" From 8976ab6722d4a1a077a243f9c5fe8aa9234bcd04 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jan 2024 08:31:06 -0500 Subject: [PATCH 0155/1215] improvement: use the target action when generating related queries --- lib/join.ex | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/join.ex b/lib/join.ex index 3a41c96c..e3c8cc4f 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -208,11 +208,21 @@ defmodule AshPostgres.Join do is_subquery? \\ true, join_relationships? \\ false ) do + read_action = + relationship.read_action || + Ash.Resource.Info.primary_action!(relationship.destination, :read).name + + context = (bindings || root_query.__ash_bindings__).context + resource |> Ash.Query.new(nil, base_filter?: false) |> Ash.Query.set_context(%{data_layer: %{start_bindings_at: start_binding}}) - |> Ash.Query.set_context((bindings || root_query.__ash_bindings__).context) + |> Ash.Query.set_context(context) |> Ash.Query.set_context(relationship.context) + |> Ash.Query.for_read(read_action, %{}, + actor: context[:private][:actor], + tenant: context[:private][:tenant] + ) |> case do %{valid?: true} = query -> ash_query = query From 76e23b186f279f7dc411e2b7e2d00303211573d5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Jan 2024 07:22:25 -0500 Subject: [PATCH 0156/1215] test: add some tests for aggregate refs in calcs --- .formatter.exs | 1 - mix.exs | 2 +- mix.lock | 2 +- test/calculation_test.exs | 36 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 12 +++++++++++- 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index 95e0deb8..372d59dc 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -26,7 +26,6 @@ spark_locals_without_parens = [ on_delete: 1, on_update: 1, polymorphic?: 1, - polymorphic_name: 1, polymorphic_on_delete: 1, polymorphic_on_update: 1, prefix: 1, diff --git a/mix.exs b/mix.exs index b1e88c24..9cb45aee 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.20")}, + {:ash, ash_version("~> 2.17 and >= 2.17.23")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 1c1a780d..4d8ae645 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.20", "8b201335fac2f9ec8eb89c71c7c9007d11a09089dd82aa070ed4214c7ae02400", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c89da37cf7464803b09cdd6f20c0b944764ea124b782cdfc72eeb9ac43a11445"}, + "ash": {:hex, :ash, "2.17.23", "a37aed1fa1f8a98f9d323c8fabf0eabd2fe24f4ccaea368f332bccf2fda21e9b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "72ea3b60ecb39e10fbb862184f5361b8bc28465ebb72da9f961afa0abdc9d27e"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 4b9e1c99..9058f01a 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -244,6 +244,42 @@ defmodule AshPostgres.CalculationTest do |> Api.read!() end + test "calculations that refer to aggregates can be authorized" do + post = + Post + |> Ash.Changeset.new(%{title: "title"}) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Api.create!() + + assert %{has_future_comment: false} = + Post + |> Ash.Query.load([:has_future_comment, :latest_comment_created_at]) + |> Ash.Query.for_read(:allow_any, %{}) + |> Api.read_one!(authorize?: true) + + assert %{has_future_comment: true} = + Post + |> Ash.Query.load([:has_future_comment, :latest_comment_created_at]) + |> Ash.Query.for_read(:allow_any, %{}) + |> Api.read_one!(authorize?: false) + + assert %{has_future_comment: false} = + Post + |> Ash.Query.for_read(:allow_any, %{}) + |> Api.read_one!() + |> Api.load!([:has_future_comment, :latest_comment_created_at], authorize?: true) + + assert %{has_future_comment: true} = + Post + |> Ash.Query.for_read(:allow_any, %{}) + |> Api.read_one!() + |> Api.load!([:has_future_comment, :latest_comment_created_at], authorize?: false) + end + test "conditional calculations can be filtered on" do author = Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 2774a914..f0110b58 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -11,6 +11,10 @@ defmodule AshPostgres.Test.Post do # Check that the post is in the same org as actor authorize_if(relates_to_actor_via([:organization, :users])) end + + policy action(:allow_any) do + authorize_if(always()) + end end postgres do @@ -45,6 +49,8 @@ defmodule AshPostgres.Test.Post do primary?(true) end + read(:allow_any) + read :paginated do pagination(offset?: true, required?: true, countable: true) end @@ -227,7 +233,11 @@ defmodule AshPostgres.Test.Post do expr(latest_arbitrary_timestamp > fragment("now()")) ) - calculate(:has_future_comment, :boolean, expr(latest_comment_created_at > fragment("now()"))) + calculate( + :has_future_comment, + :boolean, + expr(latest_comment_created_at > fragment("now()") || type(false, :boolean)) + ) calculate( :was_created_in_the_last_month, From d5d312de563273eda74fb80c54d8e6867549d943 Mon Sep 17 00:00:00 2001 From: "Eduardo B. Alexandre" Date: Tue, 9 Jan 2024 13:21:09 -0300 Subject: [PATCH 0157/1215] feat: Add unit test to check lateral joins with custom schemas and tables --- .../test_repo/entities/20240109160153.json | 59 +++++++++++++++++++ .../test_repo/records/20240109160153.json | 59 +++++++++++++++++++ .../temp_entities/20240109160153.json | 59 +++++++++++++++++++ .../20240109155951_create_temp_schema.exs | 11 ++++ .../20240109160153_migrate_resources14.exs | 40 +++++++++++++ test/load_test.exs | 11 +++- test/support/registry.ex | 3 + test/support/resources/entity.ex | 29 +++++++++ test/support/resources/record.ex | 35 +++++++++++ test/support/resources/temp_entity.ex | 24 ++++++++ 10 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/entities/20240109160153.json create mode 100644 priv/resource_snapshots/test_repo/records/20240109160153.json create mode 100644 priv/resource_snapshots/test_repo/temp_entities/20240109160153.json create mode 100644 priv/test_repo/migrations/20240109155951_create_temp_schema.exs create mode 100644 priv/test_repo/migrations/20240109160153_migrate_resources14.exs create mode 100644 test/support/resources/entity.ex create mode 100644 test/support/resources/record.ex create mode 100644 test/support/resources/temp_entity.ex diff --git a/priv/resource_snapshots/test_repo/entities/20240109160153.json b/priv/resource_snapshots/test_repo/entities/20240109160153.json new file mode 100644 index 00000000..c3760af7 --- /dev/null +++ b/priv/resource_snapshots/test_repo/entities/20240109160153.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "entities", + "hash": "B3F53BBC4C888000775A43EBF4B52EA9CFCF37C82A9F1FF50BBDEDB78015DC54", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": false +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/records/20240109160153.json b/priv/resource_snapshots/test_repo/records/20240109160153.json new file mode 100644 index 00000000..f8fb135e --- /dev/null +++ b/priv/resource_snapshots/test_repo/records/20240109160153.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "records", + "hash": "C33734A021F713C5331B6306A94EDFF918808A62860014D0FF1F39DAF323D5A6", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json b/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json new file mode 100644 index 00000000..f7a24932 --- /dev/null +++ b/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "temp_entities", + "hash": "8E78A1B3B35715F479FDA45768524A8B1F707EDF833F2B8E9A128C13FE719995", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": "temp", + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": false +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240109155951_create_temp_schema.exs b/priv/test_repo/migrations/20240109155951_create_temp_schema.exs new file mode 100644 index 00000000..2ecb9500 --- /dev/null +++ b/priv/test_repo/migrations/20240109155951_create_temp_schema.exs @@ -0,0 +1,11 @@ +defmodule AshPostgres.TestRepo.Migrations.CreateTempSchema do + use Ecto.Migration + + def up do + execute("create schema if not exists \"temp\"") + end + + def down do + execute("drop schema if exists \"temp\"") + end +end diff --git a/priv/test_repo/migrations/20240109160153_migrate_resources14.exs b/priv/test_repo/migrations/20240109160153_migrate_resources14.exs new file mode 100644 index 00000000..22bec37b --- /dev/null +++ b/priv/test_repo/migrations/20240109160153_migrate_resources14.exs @@ -0,0 +1,40 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources14 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 + create table(:temp_entities, primary_key: false, prefix: "temp") do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :full_name, :text, null: false + add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()") + add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()") + end + + create table(:records, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :full_name, :text, null: false + add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()") + add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()") + end + + create table(:entities, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :full_name, :text, null: false + add :inserted_at, :utc_datetime_usec, null: false, default: fragment("now()") + add :updated_at, :utc_datetime_usec, null: false, default: fragment("now()") + end + end + + def down do + drop table(:entities) + + drop table(:records) + + drop table(:temp_entities, prefix: "temp") + end +end \ No newline at end of file diff --git a/test/load_test.exs b/test/load_test.exs index f70a2e95..37abe997 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Comment, Post} + alias AshPostgres.Test.{Api, Comment, Post, TempEntity, Record} require Ash.Query @@ -334,5 +334,14 @@ defmodule AshPostgres.Test.LoadTest do assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results end + + test "lateral join loads with read action from a custom table and schema" do + record = Record |> Ash.Changeset.new(%{full_name: "name"}) |> Api.create!() + temp_entity = TempEntity |> Ash.Changeset.new(%{full_name: "name"}) |> Api.create!() + + assert %{entity: entity} = Api.load!(record, :entity) + + assert temp_entity.id == entity.id + end end end diff --git a/test/support/registry.ex b/test/support/registry.ex index fe0544c4..2d855103 100644 --- a/test/support/registry.ex +++ b/test/support/registry.ex @@ -15,5 +15,8 @@ defmodule AshPostgres.Test.Registry do entry(AshPostgres.Test.Account) entry(AshPostgres.Test.Organization) entry(AshPostgres.Test.Manager) + entry(AshPostgres.Test.Entity) + entry(AshPostgres.Test.TempEntity) + entry(AshPostgres.Test.Record) end end diff --git a/test/support/resources/entity.ex b/test/support/resources/entity.ex new file mode 100644 index 00000000..01571e2e --- /dev/null +++ b/test/support/resources/entity.ex @@ -0,0 +1,29 @@ +defmodule AshPostgres.Test.Entity do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + uuid_primary_key :id + + attribute :full_name, :string, allow_nil?: false + + timestamps(private?: false) + end + + postgres do + table "entities" + repo AshPostgres.TestRepo + end + + actions do + defaults [:create, :read] + + read :read_from_temp do + prepare fn query, _ -> + Ash.Query.set_context(query, %{data_layer: %{table: "temp_entities", schema: "temp"}}) + end + end + end +end diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex new file mode 100644 index 00000000..7c4676c4 --- /dev/null +++ b/test/support/resources/record.ex @@ -0,0 +1,35 @@ +defmodule AshPostgres.Test.Record do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + uuid_primary_key :id + + attribute :full_name, :string, allow_nil?: false + + timestamps(private?: false) + end + + relationships do + alias AshPostgres.Test.Entity + + has_one :entity, Entity do + no_attributes? true + + read_action :read_from_temp + + filter expr(full_name == parent(full_name)) + end + end + + postgres do + table "records" + repo AshPostgres.TestRepo + end + + actions do + defaults [:create, :read] + end +end diff --git a/test/support/resources/temp_entity.ex b/test/support/resources/temp_entity.ex new file mode 100644 index 00000000..d32a0bc6 --- /dev/null +++ b/test/support/resources/temp_entity.ex @@ -0,0 +1,24 @@ +defmodule AshPostgres.Test.TempEntity do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + uuid_primary_key :id + + attribute :full_name, :string, allow_nil?: false + + timestamps(private?: false) + end + + postgres do + table "temp_entities" + schema "temp" + repo AshPostgres.TestRepo + end + + actions do + defaults [:create, :read] + end +end From 8bfa3ffa08d242bf5f860e3603f20c01fc9981cc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Jan 2024 08:19:21 -0500 Subject: [PATCH 0158/1215] fix: don't overwrite manually set schema on lateral join query --- lib/data_layer.ex | 54 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3863dbdf..6c5c1e2c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1170,32 +1170,36 @@ defmodule AshPostgres.DataLayer do def set_subquery_prefix(data_layer_query, source_query, resource) do config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() - query_tenant = - case source_query do - %{__tenant__: tenant} -> tenant - %{tenant: tenant} -> tenant - _ -> nil - end - - if Ash.Resource.Info.multitenancy_strategy(resource) == :context do - %{ - data_layer_query - | prefix: - to_string( - query_tenant || AshPostgres.DataLayer.Info.schema(resource) || - config[:default_prefix] || - "public" - ) - } + if data_layer_query.__ash_bindings__.context[:data_layer][:schema] do + data_layer_query else - %{ - data_layer_query - | prefix: - to_string( - AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || - "public" - ) - } + query_tenant = + case source_query do + %{__tenant__: tenant} -> tenant + %{tenant: tenant} -> tenant + _ -> nil + end + + if Ash.Resource.Info.multitenancy_strategy(resource) == :context do + %{ + data_layer_query + | prefix: + to_string( + query_tenant || AshPostgres.DataLayer.Info.schema(resource) || + config[:default_prefix] || + "public" + ) + } + else + %{ + data_layer_query + | prefix: + to_string( + AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || + "public" + ) + } + end end end From 9b5acd98f7b4cfa0c6c1f17714689304f48caeb1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Jan 2024 08:34:23 -0500 Subject: [PATCH 0159/1215] chore: credo, dialyzer, format --- lib/data_layer.ex | 60 ++++++++++++++------------- test/load_test.exs | 2 +- test/support/resources/entity.ex | 10 ++--- test/support/resources/record.ex | 12 +++--- test/support/resources/temp_entity.ex | 6 +-- 5 files changed, 46 insertions(+), 44 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6c5c1e2c..8fe7224c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1170,36 +1170,38 @@ defmodule AshPostgres.DataLayer do def set_subquery_prefix(data_layer_query, source_query, resource) do config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() - if data_layer_query.__ash_bindings__.context[:data_layer][:schema] do - data_layer_query - else - query_tenant = - case source_query do - %{__tenant__: tenant} -> tenant - %{tenant: tenant} -> tenant - _ -> nil - end + case data_layer_query do + %{__ash_bindings__: %{context: %{data_layer: %{schema: schema}}}} when not is_nil(schema) -> + data_layer_query - if Ash.Resource.Info.multitenancy_strategy(resource) == :context do - %{ - data_layer_query - | prefix: - to_string( - query_tenant || AshPostgres.DataLayer.Info.schema(resource) || - config[:default_prefix] || - "public" - ) - } - else - %{ - data_layer_query - | prefix: - to_string( - AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || - "public" - ) - } - end + _ -> + query_tenant = + case source_query do + %{__tenant__: tenant} -> tenant + %{tenant: tenant} -> tenant + _ -> nil + end + + if Ash.Resource.Info.multitenancy_strategy(resource) == :context do + %{ + data_layer_query + | prefix: + to_string( + query_tenant || AshPostgres.DataLayer.Info.schema(resource) || + config[:default_prefix] || + "public" + ) + } + else + %{ + data_layer_query + | prefix: + to_string( + AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || + "public" + ) + } + end end end diff --git a/test/load_test.exs b/test/load_test.exs index 37abe997..f01e95bb 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Comment, Post, TempEntity, Record} + alias AshPostgres.Test.{Api, Comment, Post, Record, TempEntity} require Ash.Query diff --git a/test/support/resources/entity.ex b/test/support/resources/entity.ex index 01571e2e..e021820a 100644 --- a/test/support/resources/entity.ex +++ b/test/support/resources/entity.ex @@ -5,9 +5,9 @@ defmodule AshPostgres.Test.Entity do data_layer: AshPostgres.DataLayer attributes do - uuid_primary_key :id + uuid_primary_key(:id) - attribute :full_name, :string, allow_nil?: false + attribute(:full_name, :string, allow_nil?: false) timestamps(private?: false) end @@ -18,12 +18,12 @@ defmodule AshPostgres.Test.Entity do end actions do - defaults [:create, :read] + defaults([:create, :read]) read :read_from_temp do - prepare fn query, _ -> + prepare(fn query, _ -> Ash.Query.set_context(query, %{data_layer: %{table: "temp_entities", schema: "temp"}}) - end + end) end end end diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index 7c4676c4..72bcbbe2 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -5,9 +5,9 @@ defmodule AshPostgres.Test.Record do data_layer: AshPostgres.DataLayer attributes do - uuid_primary_key :id + uuid_primary_key(:id) - attribute :full_name, :string, allow_nil?: false + attribute(:full_name, :string, allow_nil?: false) timestamps(private?: false) end @@ -16,11 +16,11 @@ defmodule AshPostgres.Test.Record do alias AshPostgres.Test.Entity has_one :entity, Entity do - no_attributes? true + no_attributes?(true) - read_action :read_from_temp + read_action(:read_from_temp) - filter expr(full_name == parent(full_name)) + filter(expr(full_name == parent(full_name))) end end @@ -30,6 +30,6 @@ defmodule AshPostgres.Test.Record do end actions do - defaults [:create, :read] + defaults([:create, :read]) end end diff --git a/test/support/resources/temp_entity.ex b/test/support/resources/temp_entity.ex index d32a0bc6..0b5ba447 100644 --- a/test/support/resources/temp_entity.ex +++ b/test/support/resources/temp_entity.ex @@ -5,9 +5,9 @@ defmodule AshPostgres.Test.TempEntity do data_layer: AshPostgres.DataLayer attributes do - uuid_primary_key :id + uuid_primary_key(:id) - attribute :full_name, :string, allow_nil?: false + attribute(:full_name, :string, allow_nil?: false) timestamps(private?: false) end @@ -19,6 +19,6 @@ defmodule AshPostgres.Test.TempEntity do end actions do - defaults [:create, :read] + defaults([:create, :read]) end end From f3ccb78f86dedaca636df544f42fe4bd95f359c0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 11 Jan 2024 22:30:35 -0500 Subject: [PATCH 0160/1215] improvement: support join_filters on aggregates fix: subquery relationships that have filters --- lib/aggregate.ex | 79 +++++++- lib/expr.ex | 9 +- lib/join.ex | 327 +++++++++++++++++-------------- mix.exs | 2 +- mix.lock | 2 +- test/aggregate_test.exs | 47 +++++ test/support/resources/author.ex | 6 + 7 files changed, 305 insertions(+), 167 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index b5324d6d..c615aa02 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -57,11 +57,12 @@ defmodule AshPostgres.Aggregate do result = aggregates |> Enum.reject(&already_added?(&1, query.__ash_bindings__)) - |> Enum.group_by(& &1.relationship_path) - |> Enum.flat_map(fn {path, aggregates} -> + |> Enum.group_by(&{&1.relationship_path, &1.join_filters}) + |> Enum.flat_map(fn {{path, join_filters}, aggregates} -> {can_group, cant_group} = Enum.split_with(aggregates, &can_group?(resource, &1)) - [{path, can_group}] ++ Enum.map(cant_group, &{path, [&1]}) + [{{path, join_filters}, can_group}] ++ + Enum.map(cant_group, &{{path, join_filters}, [&1]}) end) |> Enum.filter(fn {_, []} -> @@ -72,7 +73,8 @@ defmodule AshPostgres.Aggregate do end) |> Enum.reduce_while( {:ok, query, []}, - fn {[first_relationship | relationship_path], aggregates}, {:ok, query, dynamics} -> + fn {{[first_relationship | relationship_path], join_filters}, aggregates}, + {:ok, query, dynamics} -> first_relationship = Ash.Resource.Info.relationship(resource, first_relationship) is_single? = match?([_], aggregates) @@ -143,6 +145,14 @@ defmodule AshPostgres.Aggregate do true, true ), + {:ok, agg_root_query, acc} <- + apply_first_relationship_join_filters( + agg_root_query, + query, + acc, + first_relationship, + join_filters + ), agg_root_query <- Map.update!( agg_root_query, @@ -155,7 +165,8 @@ defmodule AshPostgres.Aggregate do aggregates, relationship_path, first_relationship, - is_single? + is_single?, + join_filters ), {:ok, filtered} <- maybe_filter_subquery( @@ -221,6 +232,40 @@ defmodule AshPostgres.Aggregate do end end + defp apply_first_relationship_join_filters( + agg_root_query, + query, + acc, + first_relationship, + join_filters + ) do + case join_filters[[first_relationship]] do + nil -> + {:ok, agg_root_query, acc} + + filter -> + with {:ok, agg_root_query} <- + AshPostgres.Join.join_all_relationships(agg_root_query, filter) do + agg_root_query = + AshPostgres.Expr.set_parent_path( + agg_root_query, + query + ) + + {query, acc} = + AshPostgres.Join.maybe_apply_filter( + agg_root_query, + agg_root_query, + agg_root_query.__ash_bindings__, + filter, + acc + ) + + {:ok, query, acc} + end + end + end + defp use_aggregate_name(query, aggregate_name) do {%{ query @@ -649,14 +694,24 @@ defmodule AshPostgres.Aggregate do _aggregates, relationship_path, first_relationship, - _is_single? + _is_single?, + join_filters ) do if Enum.empty?(relationship_path) do {:ok, agg_root_query} else + join_filters = + Enum.reduce(join_filters, %{}, fn {key, value}, acc -> + if List.starts_with?(key, [first_relationship.name]) do + Map.put(acc, Enum.drop(key, 1), value) + else + acc + end + end) + AshPostgres.Join.join_all_relationships( agg_root_query, - nil, + Map.values(join_filters), [], [ {:inner, @@ -667,7 +722,9 @@ defmodule AshPostgres.Aggregate do ], [], nil, - false + false, + join_filters, + agg_root_query ) end end @@ -711,10 +768,12 @@ defmodule AshPostgres.Aggregate do def optimizable_first_aggregate?(resource, %{ name: name, kind: :first, - relationship_path: relationship_path + relationship_path: relationship_path, + join_filters: join_filters }) do name in AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) || - single_path?(resource, relationship_path) + (join_filters in [nil, %{}, []] && + single_path?(resource, relationship_path)) end def optimizable_first_aggregate?(_, _), do: false diff --git a/lib/expr.ex b/lib/expr.ex index 90bf0f72..268822a3 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1388,7 +1388,7 @@ defmodule AshPostgres.Expr do aggregates: %{}, parent_stack: [ query.__ash_bindings__.resource - | query.__ash_bindings__[:parent_resources] || [] + | query.__ash_bindings__[:parent_resource] || [] ], calculations: %{}, public?: false @@ -2150,11 +2150,14 @@ defmodule AshPostgres.Expr do end @doc false - def set_parent_path(query, parent) do + def set_parent_path(query, parent, parent_is_parent_as? \\ true) do # This is a stupid name. Its actually the path we *remove* when stepping up a level. I.e the child's path Map.update!(query, :__ash_bindings__, fn ash_bindings -> ash_bindings - |> Map.put(:parent_bindings, parent.__ash_bindings__) + |> Map.put( + :parent_bindings, + parent.__ash_bindings__ |> Map.put(:parent_is_parent_as?, parent_is_parent_as?) + ) |> Map.put(:parent_resources, [ parent.__ash_bindings__.resource | parent.__ash_bindings__[:parent_resources] || [] ]) diff --git a/lib/join.ex b/lib/join.ex index e3c8cc4f..34f367cb 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -30,11 +30,23 @@ defmodule AshPostgres.Join do relationship_paths \\ nil, path \\ [], source \\ nil, - sort? \\ true + sort? \\ true, + join_filters \\ nil, + parent_bindings \\ nil ) # simple optimization for common cases - def join_all_relationships(query, filter, _opts, relationship_paths, _path, _source, _sort?) + def join_all_relationships( + query, + filter, + _opts, + relationship_paths, + _path, + _source, + _sort?, + _join_filters, + _parent_bindings + ) when is_nil(relationship_paths) and filter in [nil, true, false] do {:ok, query} end @@ -46,7 +58,9 @@ defmodule AshPostgres.Join do relationship_paths, path, source, - sort? + sort?, + join_filters, + parent_query ) do relationship_paths = cond do @@ -94,60 +108,76 @@ defmodule AshPostgres.Join do [other] end - case get_binding(source, Enum.map(current_path, & &1.name), query, look_for_join_types) do - binding when is_integer(binding) -> - case join_all_relationships( - query, - filter, - opts, - [{join_type, rest_rels}], - current_path, - source, - sort? - ) do - {:ok, query} -> - {:cont, {:ok, query}} - - {:error, error} -> - {:halt, {:error, error}} - end + binding = + get_binding(source, Enum.map(current_path, & &1.name), query, look_for_join_types) + + # We can't reuse joins if we're adding filters/have a separate parent binding + if is_nil(join_filters) && is_nil(parent_query) && binding do + case join_all_relationships( + query, + filter, + opts, + [{join_type, rest_rels}], + current_path, + source, + sort? + ) do + {:ok, query} -> + {:cont, {:ok, query}} + + {:error, error} -> + {:halt, {:error, error}} + end + else + case join_relationship( + set_parent_bindings(query, parent_query), + relationship, + Enum.map(path, & &1.name), + current_join_type, + source, + filter, + sort?, + Ash.Filter.move_to_relationship_path( + join_filters[Enum.map(current_path, & &1.name)], + [relationship.name] + ) + ) do + {:ok, joined_query} -> + joined_query_with_distinct = add_distinct(relationship, join_type, joined_query) + + case join_all_relationships( + joined_query_with_distinct, + filter, + opts, + [{join_type, rest_rels}], + current_path, + source, + sort?, + join_filters, + joined_query + ) do + {:ok, query} -> + {:cont, {:ok, query}} + + {:error, error} -> + {:halt, {:error, error}} + end - nil -> - case join_relationship( - query, - relationship, - Enum.map(path, & &1.name), - current_join_type, - source, - filter, - sort? - ) do - {:ok, joined_query} -> - joined_query_with_distinct = add_distinct(relationship, join_type, joined_query) - - case join_all_relationships( - joined_query_with_distinct, - filter, - opts, - [{join_type, rest_rels}], - current_path, - source, - sort? - ) do - {:ok, query} -> - {:cont, {:ok, query}} - - {:error, error} -> - {:halt, {:error, error}} - end - - {:error, error} -> - {:halt, {:error, error}} - end + {:error, error} -> + {:halt, {:error, error}} + end end end) end + defp set_parent_bindings(query, parent_query) do + if parent_query do + AshPostgres.Expr.set_parent_path(query, parent_query, false) + else + query + end + end + defp to_joins(paths, filter, resource) do paths |> Enum.reject(&(&1 == [])) @@ -287,6 +317,17 @@ defmodule AshPostgres.Join do query -> {:error, query} end + |> case do + {:ok, query, acc} -> + if Enum.empty?(query.joins) do + {:ok, query, acc} + else + {:ok, subquery(query), acc} + end + + {:error, error} -> + {:error, error} + end end defp do_relationship_sort( @@ -541,42 +582,14 @@ defmodule AshPostgres.Join do end defp join_relationship( - query, - relationship, - path, - join_type, - source, - filter, - sort? - ) do - case Map.get(query.__ash_bindings__.bindings, path) do - %{type: existing_join_type} when join_type != existing_join_type -> - raise "unreachable?" - - nil -> - do_join_relationship( - query, - relationship, - path, - join_type, - source, - filter, - sort? - ) - - _ -> - {:ok, query} - end - end - - defp do_join_relationship( query, %{manual: {module, opts}} = relationship, path, kind, source, filter, - sort? + sort?, + apply_filter ) do full_path = path ++ [relationship.name] initial_ash_bindings = query.__ash_bindings__ @@ -594,72 +607,70 @@ defmodule AshPostgres.Join do query.__ash_bindings__ end - case maybe_get_resource_query( - relationship.destination, - relationship, - query, - sort?, - full_path, - root_bindings - ) do - {:error, error} -> - {:error, error} + with {:ok, relationship_destination, acc} <- + maybe_get_resource_query( + relationship.destination, + relationship, + query, + sort?, + full_path, + root_bindings + ) do + {relationship_destination, acc} = + relationship_destination + |> Ecto.Queryable.to_query() + |> set_join_prefix(query, relationship.destination) + |> maybe_apply_filter(query, root_bindings, apply_filter, acc) - {:ok, relationship_destination, acc} -> - relationship_destination = - relationship_destination - |> Ecto.Queryable.to_query() - |> set_join_prefix(query, relationship.destination) + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + binding_kinds = + case kind do + :left -> + [:left, :inner] - binding_kinds = - case kind do - :left -> - [:left, :inner] + :inner -> + [:left, :inner] - :inner -> - [:left, :inner] + other -> + [other] + end - other -> - [other] + current_binding = + Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> + if data.type in binding_kinds && data.path == path do + binding end + end) - current_binding = - Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> - if data.type in binding_kinds && data.path == path do - binding - end - end) - - needs_subquery? = - used_aggregates != [] || Map.get(relationship, :from_many?) - - relationship_destination = - if needs_subquery? do - subquery(from(row in relationship_destination, limit: 1)) - else - relationship_destination - end + needs_subquery? = + used_aggregates != [] || Map.get(relationship, :from_many?) - case module.ash_postgres_join( - query, - opts, - current_binding, - initial_ash_bindings.current, - kind, - relationship_destination - ) do - {:ok, query} -> - AshPostgres.Aggregate.add_aggregates( - query, - used_aggregates, - relationship.destination, - false, - initial_ash_bindings.current, - {query.__ash_bindings__.resource, full_path} - ) + relationship_destination = + if needs_subquery? do + subquery(from(row in relationship_destination, limit: 1)) + else + relationship_destination end + + case module.ash_postgres_join( + query, + opts, + current_binding, + initial_ash_bindings.current, + kind, + relationship_destination + ) do + {:ok, query} -> + AshPostgres.Aggregate.add_aggregates( + query, + used_aggregates, + relationship.destination, + false, + initial_ash_bindings.current, + {query.__ash_bindings__.resource, full_path} + ) + end end rescue e in UndefinedFunctionError -> @@ -673,14 +684,15 @@ defmodule AshPostgres.Join do end end - defp do_join_relationship( + defp join_relationship( query, %{type: :many_to_many} = relationship, path, kind, source, filter, - sort? + sort?, + apply_filter ) do join_relationship = Ash.Resource.Info.relationship(relationship.source, relationship.join_relationship) @@ -745,20 +757,21 @@ defmodule AshPostgres.Join do path, root_bindings ) do - query = - query - |> AshPostgres.DataLayer.merge_expr_accumulator(through_acc) - |> AshPostgres.DataLayer.merge_expr_accumulator(dest_acc) - relationship_through = relationship_through |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.through) - relationship_destination = + {relationship_destination, dest_acc} = relationship_destination |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) + |> maybe_apply_filter(query, root_bindings, apply_filter, dest_acc) + + query = + query + |> AshPostgres.DataLayer.merge_expr_accumulator(through_acc) + |> AshPostgres.DataLayer.merge_expr_accumulator(dest_acc) binding_kinds = case kind do @@ -831,14 +844,15 @@ defmodule AshPostgres.Join do end end - defp do_join_relationship( + defp join_relationship( query, relationship, path, kind, source, filter, - sort? + sort?, + apply_filter ) do full_path = path ++ [relationship.name] initial_ash_bindings = query.__ash_bindings__ @@ -885,12 +899,13 @@ defmodule AshPostgres.Join do {:error, error} {:ok, relationship_destination, acc} -> - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - relationship_destination = + {relationship_destination, acc} = relationship_destination |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) + |> maybe_apply_filter(query, root_bindings, apply_filter, acc) + + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) relationship_destination = if needs_subquery? do @@ -1023,4 +1038,12 @@ defmodule AshPostgres.Join do ) end end + + @doc false + def maybe_apply_filter(query, _root_query, _bindings, nil, acc), do: {query, acc} + + def maybe_apply_filter(query, root_query, bindings, filter, acc) do + {dynamic, acc} = AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true, acc) + {from(row in query, where: ^dynamic), acc} + end end diff --git a/mix.exs b/mix.exs index 9cb45aee..e35500e2 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.23")}, + {:ash, ash_version("~> 2.17 and >= 2.17.24")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 4d8ae645..784c47e9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.23", "a37aed1fa1f8a98f9d323c8fabf0eabd2fe24f4ccaea368f332bccf2fda21e9b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "72ea3b60ecb39e10fbb862184f5361b8bc28465ebb72da9f961afa0abdc9d27e"}, + "ash": {:hex, :ash, "2.17.24", "af3785c966f6141b92bb4c9826ef46c60234b0f2f4081d978e6d871f787e88cc", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a2e8f472a900cbade23c1ec3add4ddd3e60fbff8dc4c9d6b2a184aad01ab919b"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index a7c1bdb9..9bf73567 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -49,6 +49,53 @@ defmodule AshPostgres.AggregateTest do assert read_post.count_of_comments == 1 end + describe "join filters" do + test "with no data, it does not effect the behavior" do + Author + |> Ash.Changeset.new(%{}) + |> Api.create!() + + assert [%{count_of_posts_with_better_comment: 0}] = + Author + |> Ash.Query.load(:count_of_posts_with_better_comment) + |> Api.read!() + end + + test "it properly applies join criteria" do + author = + Author + |> Ash.Changeset.new(%{}) + |> Api.create!() + + matching_post = + Post + |> Ash.Changeset.new(%{title: "match", score: 10}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + non_matching_post = + Post + |> Ash.Changeset.new(%{title: "non_match", score: 100}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "match", likes: 100}) + |> Ash.Changeset.manage_relationship(:post, matching_post, type: :append_and_remove) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "non_match", likes: 0}) + |> Ash.Changeset.manage_relationship(:post, non_matching_post, type: :append_and_remove) + |> Api.create!() + + assert [%{count_of_posts_with_better_comment: 1}] = + Author + |> Ash.Query.load(:count_of_posts_with_better_comment) + |> Api.read!() + end + end + describe "count" do test "with no related data it returns 0" do post = diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 82ef7b17..8757d4e7 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -98,4 +98,10 @@ defmodule AshPostgres.Test.Author do calculate(:has_posts, :boolean, expr(exists(posts, true))) end + + aggregates do + count :count_of_posts_with_better_comment, [:posts, :comments] do + join_filter([:posts, :comments], expr(parent(score) < likes)) + end + end end From 40c1a13652dc65406692ffbcaf6d78d1d8aa07c4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 11 Jan 2024 23:17:05 -0500 Subject: [PATCH 0161/1215] fix: unset sort/distinct on related queries --- lib/join.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/join.ex b/lib/join.ex index 34f367cb..66df64e3 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -253,6 +253,7 @@ defmodule AshPostgres.Join do actor: context[:private][:actor], tenant: context[:private][:tenant] ) + |> Ash.Query.unset([:sort, :distinct, :select, :limit, :offset]) |> case do %{valid?: true} = query -> ash_query = query From b93b1d722b580ae29dbffbd814431d08465934bc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jan 2024 10:11:16 -0500 Subject: [PATCH 0162/1215] improvement: support `all_tenants?` option for identities improvement: support `all_tenants?` option for custom indexes --- .formatter.exs | 1 + .../dsls/DSL:-AshPostgres.DataLayer.md | 1 + lib/custom_index.ex | 8 ++- .../migration_generator.ex | 38 +++++++++++--- lib/migration_generator/operation.ex | 22 +++++--- mix.exs | 2 +- mix.lock | 2 +- test/migration_generator_test.exs | 51 +++++++++++++++++++ 8 files changed, 110 insertions(+), 15 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index 372d59dc..934d4157 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,5 @@ spark_locals_without_parens = [ + all_tenants?: 1, base_filter_sql: 1, check: 1, check_constraint: 2, diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index af2b1853..6cab440c 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -112,6 +112,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" | [`where`](#postgres-custom_indexes-index-where){: #postgres-custom_indexes-index-where } | `String.t` | | specify conditions for a partial index. | | [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | | [`include`](#postgres-custom_indexes-index-include){: #postgres-custom_indexes-index-include } | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | +| [`all_tenants?`](#postgres-custom_indexes-index-all_tenants?){: #postgres-custom_indexes-index-all_tenants? } | `boolean` | `false` | Whether or not the index should factor in the multitenancy attribute or not. | diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 03a1b318..ec8121d2 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -10,7 +10,8 @@ defmodule AshPostgres.CustomIndex do :prefix, :where, :include, - :message + :message, + :all_tenants? ] defstruct @fields @@ -56,6 +57,11 @@ defmodule AshPostgres.CustomIndex do type: {:list, :string}, doc: "specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs." + ], + all_tenants?: [ + type: :boolean, + default: false, + doc: "Whether or not the index should factor in the multitenancy attribute or not." ] ] diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index ef3d0389..0406d894 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1697,7 +1697,7 @@ defmodule AshPostgres.MigrationGenerator do custom_indexes_to_remove = Enum.filter(old_snapshot.custom_indexes, fn old_custom_index -> - rewrite_all_identities? || + (rewrite_all_identities? && !old_custom_index.all_tenants?) || !Enum.find(snapshot.custom_indexes, fn index -> indexes_match?(snapshot.table, old_custom_index, index) end) @@ -1714,13 +1714,14 @@ defmodule AshPostgres.MigrationGenerator do unique_indexes_to_remove = if rewrite_all_identities? do - old_snapshot.identities + Enum.reject(old_snapshot.identities, & &1.all_tenants?) else Enum.reject(old_snapshot.identities, fn old_identity -> Enum.find(snapshot.identities, fn identity -> identity.name == old_identity.name && Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && - old_identity.base_filter == identity.base_filter + old_identity.base_filter == identity.base_filter && + old_identity.all_tenants? == identity.all_tenants? end) end) end @@ -1734,7 +1735,17 @@ defmodule AshPostgres.MigrationGenerator do unique_indexes_to_rename = if rewrite_all_identities? do - [] + snapshot.identities + |> Enum.filter(& &1.all_tenants?) + |> Enum.map(fn identity -> + Enum.find_value(old_snapshot.identities, fn old_identity -> + if old_identity.name == identity.name && + old_identity.index_name != identity.index_name do + {old_identity, identity} + end + end) + end) + |> Enum.filter(& &1) else snapshot.identities |> Enum.map(fn identity -> @@ -1759,12 +1770,25 @@ defmodule AshPostgres.MigrationGenerator do unique_indexes_to_add = if rewrite_all_identities? do snapshot.identities + |> Enum.reject(fn identity -> + if identity.all_tenants? do + Enum.find(old_snapshot.identities, fn old_identity -> + old_identity.name == identity.name && + Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && + old_identity.base_filter == identity.base_filter && + old_identity.all_tenants? == identity.all_tenants? + end) + else + false + end + end) else Enum.reject(snapshot.identities, fn identity -> Enum.find(old_snapshot.identities, fn old_identity -> old_identity.name == identity.name && Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && - old_identity.base_filter == identity.base_filter + old_identity.base_filter == identity.base_filter && + old_identity.all_tenants? == identity.all_tenants? end) end) end @@ -2684,7 +2708,7 @@ defmodule AshPostgres.MigrationGenerator do end) end) |> Enum.sort_by(& &1.name) - |> Enum.map(&Map.take(&1, [:name, :keys])) + |> Enum.map(&Map.take(&1, [:name, :keys, :all_tenants?])) |> Enum.map(fn %{keys: keys} = identity -> %{ identity @@ -2864,6 +2888,7 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put_new(:fields, []) |> Map.put_new(:include, []) |> Map.put_new(:message, nil) + |> Map.put_new(:all_tenants?, false) end) end @@ -3010,6 +3035,7 @@ defmodule AshPostgres.MigrationGenerator do end) |> add_index_name(table) |> Map.put_new(:base_filter, nil) + |> Map.put_new(:all_tenants?, false) end defp add_index_name(%{name: name} = index, table) do diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 86f5f113..6af3ce63 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -769,18 +769,28 @@ defmodule AshPostgres.MigrationGenerator.Operation do import Helper def up(%{ - identity: %{name: name, keys: keys, base_filter: base_filter, index_name: index_name}, + identity: %{ + name: name, + keys: keys, + base_filter: base_filter, + index_name: index_name, + all_tenants?: all_tenants? + }, table: table, schema: schema, multitenancy: multitenancy }) do keys = - case multitenancy.strategy do - :attribute -> - [multitenancy.attribute | keys] + if all_tenants? do + keys + else + case multitenancy.strategy do + :attribute -> + [multitenancy.attribute | keys] - _ -> - keys + _ -> + keys + end end index_name = index_name || "#{table}_#{name}_index" diff --git a/mix.exs b/mix.exs index e35500e2..34be7fb6 100644 --- a/mix.exs +++ b/mix.exs @@ -204,7 +204,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.17 and >= 2.17.24")}, + {:ash, ash_version("~> 2.18")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 784c47e9..d9f98896 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.17.24", "af3785c966f6141b92bb4c9826ef46c60234b0f2f4081d978e6d871f787e88cc", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a2e8f472a900cbade23c1ec3add4ddd3e60fbff8dc4c9d6b2a184aad01ab919b"}, + "ash": {:hex, :ash, "2.18.0", "498f3c1766d2343530035a3bae40dffb89eb721531c4b3375b51cdd175dfb4db", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb61da47f0e98ea6cb3dd3e3e81a91bfb4c189afbc69c44e2c4b41dea8d1f6f"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index e9d8fc01..e1ba37e4 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -942,6 +942,57 @@ defmodule AshPostgres.MigrationGeneratorTest do ~S{references(:users, column: :secondary_id, with: [related_key_id: :key_id, org_id: :org_id], match: :full, name: "user_things_user_id_fkey", type: :uuid, prefix: "public")} end + test "identities using `all_tenants?: true` will not have the condition on multitenancy attribtue added" do + defresource Org, "orgs" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + end + + defresource User, "users" do + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:secondary_id, :uuid) + attribute(:name, :string) + attribute(:org_id, :uuid) + attribute(:key_id, :uuid) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + identities do + identity(:unique_name, [:name], all_tenants?: true) + end + + relationships do + belongs_to(:org, Org) + end + end + + defapi([Org, User]) + + AshPostgres.MigrationGenerator.generate(Api, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ + ~S{create unique_index(:users, [:name], name: "users_unique_name_index")} + end + test "when modified, the foreign key is dropped before modification" do defposts do attributes do From 21702052aa89a54a431ad35f4fac85e31ceb8bd0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jan 2024 10:11:42 -0500 Subject: [PATCH 0163/1215] chore: release version v1.4.0 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1efa352c..4611455e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.4.0](https://github.com/ash-project/ash_postgres/compare/v1.3.68...v1.4.0) (2024-01-12) + + + + +### Features: + +* Add unit test to check lateral joins + +### Bug Fixes: + +* unset sort/distinct on related queries + +* subquery relationships that have filters + +* don't overwrite manually set schema on lateral join query + +* properly configure `polymorphic_name` option + +* honor configured schema on bulk create + +### Improvements: + +* support `all_tenants?` option for identities + +* support `all_tenants?` option for custom indexes + +* support join_filters on aggregates + +* use the target action when generating related queries + ## [v1.3.68](https://github.com/ash-project/ash_postgres/compare/v1.3.67...v1.3.68) (2024-01-04) diff --git a/mix.exs b/mix.exs index 34be7fb6..8f7b0f78 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.3.68" + @version "1.4.0" def project do [ From b4d9fd1925aee5f9c18761b9d338bab1f48cc7ab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jan 2024 14:27:14 -0500 Subject: [PATCH 0164/1215] docs: clean up hexdocs --- mix.exs | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/mix.exs b/mix.exs index 8f7b0f78..2ada7ad3 100644 --- a/mix.exs +++ b/mix.exs @@ -133,26 +133,10 @@ defmodule AshPostgres.MixProject do """ end end, - spark: [ - mix_tasks: [ - Postgres: [ - Mix.Tasks.AshPostgres.GenerateMigrations, - Mix.Tasks.AshPostgres.Create, - Mix.Tasks.AshPostgres.Drop, - Mix.Tasks.AshPostgres.Migrate, - Mix.Tasks.AshPostgres.Rollback - ] - ], - extensions: [ - %{ - module: AshPostgres.DataLayer, - name: "AshPostgres", - target: "Ash.Resource", - type: "DataLayer" - } - ] - ], groups_for_extras: groups_for_extras(), + nest_modules_by_prefix: [ + AshPostgres.Functions + ], groups_for_modules: [ AshPostgres: [ AshPostgres, @@ -191,8 +175,7 @@ defmodule AshPostgres.MixProject do AshPostgres.Functions.ILike, AshPostgres.Functions.Like, AshPostgres.Functions.VectorCosineDistance - ], - Internals: ~r/.*/ + ] ] ] end From 717a630a8423d53e059b62af93696c4421dd4959 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jan 2024 14:28:01 -0500 Subject: [PATCH 0165/1215] chore: update ex_doc --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index d9f98896..2b8e2cf7 100644 --- a/mix.lock +++ b/mix.lock @@ -10,14 +10,14 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, - "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "16a8f536d1a0868293a30d63bcff6510bf023de3", []}, + "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, From 55d319c66ca8449488e92c4d2913b4df68059678 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jan 2024 14:48:41 -0500 Subject: [PATCH 0166/1215] chore: simplify doc selection --- mix.exs | 66 ++++++++++++++++----------------------------------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/mix.exs b/mix.exs index 2ada7ad3..c8da1b0f 100644 --- a/mix.exs +++ b/mix.exs @@ -66,58 +66,11 @@ defmodule AshPostgres.MixProject do ] end - defp extras() do - "documentation/**/*.{md,livemd,cheatmd}" - |> Path.wildcard() - |> Enum.map(fn path -> - title = - path - |> Path.basename(".md") - |> Path.basename(".livemd") - |> Path.basename(".cheatmd") - |> String.split(~r/[-_]/) - |> Enum.map_join(" ", &capitalize/1) - |> case do - "F A Q" -> - "FAQ" - - other -> - other - end - - {String.to_atom(path), - [ - title: title - ]} - end) - end - - defp capitalize(string) do - string - |> String.split(" ") - |> Enum.map(fn string -> - [hd | tail] = String.graphemes(string) - String.capitalize(hd) <> Enum.join(tail) - end) - end - - defp groups_for_extras() do - [ - Tutorials: [ - ~r'documentation/tutorials' - ], - "How To": ~r'documentation/how_to', - Topics: ~r'documentation/topics', - DSLs: ~r'documentation/dsls' - ] - end - defp docs do [ main: "get-started-with-postgres", source_ref: "v#{@version}", logo: "logos/small-logo.png", - extras: extras(), before_closing_head_tag: fn type -> if type == :html do """ @@ -133,7 +86,24 @@ defmodule AshPostgres.MixProject do """ end end, - groups_for_extras: groups_for_extras(), + extras: [ + "documentation/tutorials/get-started-with-postgres.md", + "documentation/how_to/join-manual-relationships.md", + "documentation/how_to/test-with-postgres.md", + "documentation/how_to/using-fragments.md", + "documentation/topics/migrations_and_tasks.md", + "documentation/topics/polymorphic_resources.md", + "documentation/topics/postgres-expressions.md", + "documentation/topics/references.md", + "documentation/topics/schema-based-multitenancy.md", + "documentation/dsls/DSL:-AshPostgres.DataLayer.md" + ], + groups_for_extras: [ + Tutorials: ~r'documentation/tutorials', + "How To": ~r'documentation/how_to', + Topics: ~r'documentation/topics', + DSLs: ~r'documentation/dsls' + ], nest_modules_by_prefix: [ AshPostgres.Functions ], From 62a640ecb7ce42f1887d7377f5a47e1fdcc399c7 Mon Sep 17 00:00:00 2001 From: Jechol Lee Date: Mon, 15 Jan 2024 19:40:05 +0900 Subject: [PATCH 0167/1215] fix: Support all_tenants? in custom index (#194) --- lib/migration_generator/operation.ex | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 6af3ce63..c22f381a 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -877,13 +877,19 @@ defmodule AshPostgres.MigrationGenerator.Operation do base_filter: base_filter, multitenancy: multitenancy }) do + keys = Enum.map(index.fields, &to_string/1) + keys = - case multitenancy.strategy do - :attribute -> - [to_string(multitenancy.attribute) | Enum.map(index.fields, &to_string/1)] + if index.all_tenants? do + keys + else + case multitenancy.strategy do + :attribute -> + [to_string(multitenancy.attribute) | keys] - _ -> - Enum.map(index.fields, &to_string/1) + _ -> + keys + end end index = From 97847da734b5cb5a37374ffe8f7f1692e4b1598d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jan 2024 23:44:49 -0500 Subject: [PATCH 0168/1215] fix: include explicit schema in snapshot folder name closes #193 --- .../migration_generator.ex | 52 +++++++++++++++---- test/migration_generator_test.exs | 2 +- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 0406d894..63b58489 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -474,7 +474,10 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.uniq_by(&{&1.table, &1.schema}) |> Enum.map(fn snapshot -> if opts.no_shell? do - Enum.find(existing_snapshots, &(&1.table == snapshot.table)) + Enum.find( + existing_snapshots, + &(&1.table == snapshot.table && &1.schema == snapshot.schema) + ) else get_existing_snapshot(snapshot, opts) end @@ -910,7 +913,12 @@ defmodule AshPostgres.MigrationGenerator do |> Path.join(repo_name) end - snapshot_file = Path.join(snapshot_folder, "#{snapshot.table}/#{timestamp()}.json") + snapshot_file = + if snapshot.schema do + Path.join(snapshot_folder, "#{snapshot.schema}.#{snapshot.table}/#{timestamp()}.json") + else + Path.join(snapshot_folder, "#{snapshot.table}/#{timestamp()}.json") + end File.mkdir_p(Path.dirname(snapshot_file)) File.write!(snapshot_file, snapshot_binary, []) @@ -2212,7 +2220,18 @@ defmodule AshPostgres.MigrationGenerator do |> Path.join(repo_name) end - snapshot_folder = Path.join(folder, snapshot.table) + snapshot_folder = + if snapshot.schema do + schema_dir = Path.join(folder, "#{snapshot.schema}.#{snapshot.table}") + + if File.dir?(schema_dir) do + schema_dir + else + Path.join(folder, snapshot.table) + end + else + Path.join(folder, snapshot.table) + end if File.exists?(snapshot_folder) do snapshot_folder @@ -2246,12 +2265,27 @@ defmodule AshPostgres.MigrationGenerator do end defp get_old_snapshot(folder, snapshot) do - old_snapshot_file = Path.join(folder, "#{snapshot.table}.json") - # This is adapter code for the old version, where migrations were stored in a flat directory - if File.exists?(old_snapshot_file) do - old_snapshot_file - |> File.read!() - |> load_snapshot() + schema_file = + if snapshot.schema do + old_snapshot_file = Path.join(folder, "#{snapshot.schema}.#{snapshot.table}.json") + + if File.exists?(old_snapshot_file) do + old_snapshot_file + |> File.read!() + |> load_snapshot() + end + end + + if schema_file do + schema_file + else + old_snapshot_file = Path.join(folder, "#{snapshot.table}.json") + # This is adapter code for the old version, where migrations were stored in a flat directory + if File.exists?(old_snapshot_file) do + old_snapshot_file + |> File.read!() + |> load_snapshot() + end end end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index e1ba37e4..ce97eb6c 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -222,7 +222,7 @@ defmodule AshPostgres.MigrationGeneratorTest do test "the migration sets up resources correctly" do # the snapshot exists and contains valid json - assert File.read!(Path.wildcard("test_snapshots_path/test_repo/posts/*.json")) + assert File.read!(Path.wildcard("test_snapshots_path/test_repo/example.posts/*.json")) |> Jason.decode!(keys: :atoms!) assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") From f20a07f54bad6c528d295d0fa32087a153692181 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jan 2024 00:12:50 -0500 Subject: [PATCH 0169/1215] improvement: support latest ash changes --- .tool-versions | 2 +- lib/sort.ex | 15 +++++++++++---- test/support/resources/post.ex | 4 ++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.tool-versions b/.tool-versions index 44acbf0f..0a06d5a6 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 26.0.2 -elixir 1.15.4 +elixir 1.16.0 diff --git a/lib/sort.ex b/lib/sort.ex index dee61655..fa345263 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -48,7 +48,16 @@ defmodule AshPostgres.Sort do calcs = Enum.flat_map(sort, fn {%Ash.Query.Calculation{} = calculation, _} -> - [calculation] + {:ok, expression} = + calculation.opts + |> calculation.module.expression(calculation.context) + |> Ash.Filter.hydrate_refs(%{ + resource: resource, + parent_stack: query.__ash_bindings__[:parent_resources] || [], + public?: false + }) + + [{calculation, Ash.Filter.move_to_relationship_path(expression, relationship_path)}] _ -> [] @@ -59,7 +68,7 @@ defmodule AshPostgres.Sort do query, %Ash.Filter{ resource: resource, - expression: calcs + expression: Enum.map(calcs, &elem(&1, 1)) }, left_only?: true ) @@ -84,9 +93,7 @@ defmodule AshPostgres.Sort do |> calc.module.expression(calc.context) |> Ash.Filter.hydrate_refs(%{ resource: resource, - aggregates: query.__ash_bindings__.aggregate_defs, parent_stack: query.__ash_bindings__[:parent_resources] || [], - calculations: %{}, public?: false }) |> Ash.Filter.move_to_relationship_path(relationship_path) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index f0110b58..d21d4ebc 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -239,6 +239,8 @@ defmodule AshPostgres.Test.Post do expr(latest_comment_created_at > fragment("now()") || type(false, :boolean)) ) + calculate(:price_times_2, :integer, expr(price * 2)) + calculate( :was_created_in_the_last_month, :boolean, @@ -333,6 +335,8 @@ defmodule AshPostgres.Test.Post do uniq?(true) end + count(:count_of_ratings, :ratings) + list :comment_titles_with_5_likes, :comments, :title do sort(title: :asc_nils_last) filter(expr(likes >= 5)) From 445de282c0df8d8cf0f6dac8be41360bd45ca705 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jan 2024 20:13:42 -0500 Subject: [PATCH 0170/1215] fix: remap selected fields, don't subquery in aggregate joins --- lib/aggregate.ex | 51 ++++++++++++++++++++++++++++++++++------------ lib/calculation.ex | 46 +++++++++++++++++++++++++++++++++++++++++ lib/data_layer.ex | 44 ++++++++++++++++++++++++++++++++++----- lib/expr.ex | 30 +++++++++++++++++++++++++-- lib/join.ex | 32 ++++++++--------------------- 5 files changed, 159 insertions(+), 44 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index c615aa02..6621de90 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -24,18 +24,17 @@ defmodule AshPostgres.Aggregate do {:ok, aggregates} -> query = AshPostgres.DataLayer.default_bindings(query, resource) - {query, aggregates, aggregate_name_mapping} = + {query, aggregates} = Enum.reduce( aggregates, - {query, [], %{}}, - fn aggregate, {query, aggregates, aggregate_name_mapping} -> + {query, []}, + fn aggregate, {query, aggregates} -> if is_atom(aggregate.name) do - {query, [aggregate | aggregates], aggregate_name_mapping} + {query, [aggregate | aggregates]} else {query, name} = use_aggregate_name(query, aggregate.name) - {query, [%{aggregate | name: name} | aggregates], - Map.put(aggregate_name_mapping, name, aggregate.name)} + {query, [%{aggregate | name: name} | aggregates]} end end ) @@ -75,7 +74,15 @@ defmodule AshPostgres.Aggregate do {:ok, query, []}, fn {{[first_relationship | relationship_path], join_filters}, aggregates}, {:ok, query, dynamics} -> - first_relationship = Ash.Resource.Info.relationship(resource, first_relationship) + first_relationship = + case Ash.Resource.Info.relationship(resource, first_relationship) do + nil -> + raise "No such relationship for #{inspect(first_relationship)} aggregates #{inspect aggregates}" + + first_relationship -> + first_relationship + end + is_single? = match?([_], aggregates) cond do @@ -154,10 +161,9 @@ defmodule AshPostgres.Aggregate do join_filters ), agg_root_query <- - Map.update!( + set_in_group( agg_root_query, - :__ash_bindings__, - &Map.put(&1, :in_group?, true) + resource ), {:ok, joined} <- join_all_relationships( @@ -221,7 +227,7 @@ defmodule AshPostgres.Aggregate do case result do {:ok, query, dynamics} -> - {:ok, add_aggregate_selects(query, dynamics, aggregate_name_mapping)} + {:ok, add_aggregate_selects(query, dynamics)} {:error, error} -> {:error, error} @@ -232,6 +238,25 @@ defmodule AshPostgres.Aggregate do end end + defp set_in_group(%{__ash_bindings__: _} = query, _resource) do + Map.update!( + query, + :__ash_bindings__, + &Map.put(&1, :in_group?, true) + ) + end + + defp set_in_group(%Ecto.SubQuery{} = subquery, resource) do + subquery = from(row in subquery, []) + + subquery + |> AshPostgres.DataLayer.default_bindings(resource) + |> Map.update!( + :__ash_bindings__, + &Map.put(&1, :in_group?, true) + ) + end + defp apply_first_relationship_join_filters( agg_root_query, query, @@ -797,7 +822,7 @@ defmodule AshPostgres.Aggregate do end) end - defp add_aggregate_selects(query, dynamics, name_mapping) do + defp add_aggregate_selects(query, dynamics) do {in_aggregates, in_body} = Enum.split_with(dynamics, fn {load, _name, _dynamic} -> is_nil(load) end) @@ -815,7 +840,7 @@ defmodule AshPostgres.Aggregate do aggs, :aggregates, Map.new(in_aggregates, fn {_, name, dynamic} -> - {name_mapping[name] || name, dynamic} + {name, dynamic} end) ) end diff --git a/lib/calculation.ex b/lib/calculation.ex index bd4e0922..f896698d 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -3,6 +3,10 @@ defmodule AshPostgres.Calculation do require Ecto.Query + @next_calculation_names Enum.reduce(0..999, %{}, fn i, acc -> + Map.put(acc, :"calculation_#{i}", :"calculation_#{i + 1}") + end) + def add_calculations(query, [], _, _, _select?), do: {:ok, query} def add_calculations(query, calculations, resource, source_binding, select?) do @@ -27,6 +31,21 @@ defmodule AshPostgres.Calculation do end) |> Enum.uniq() + {query, calculations} = + Enum.reduce( + calculations, + {query, []}, + fn {calculation, expression}, {query, calculations} -> + if is_atom(calculation.name) do + {query, [{calculation, expression} | calculations]} + else + {query, name} = use_calculation_name(query, calculation.name) + + {query, [{%{calculation | name: name}, expression} | calculations]} + end + end + ) + case AshPostgres.Aggregate.add_aggregates( query, aggregates, @@ -90,6 +109,33 @@ defmodule AshPostgres.Calculation do end end + def next_calculation_name(i) do + @next_calculation_names[i] || + raise Ash.Error.Framework.AssumptionFailed, + message: """ + All 1000 static names for calculations have been used in a single query. + Congratulations, this means that you have gone so wildly beyond our imagination + of how much can fit into a single quer. Please file an issue and we will raise the limit. + """ + end + + defp use_calculation_name(query, aggregate_name) do + {%{ + query + | __ash_bindings__: %{ + query.__ash_bindings__ + | current_calculation_name: + next_calculation_name(query.__ash_bindings__.current_calculation_name), + calculation_names: + Map.put( + query.__ash_bindings__.calculation_names, + aggregate_name, + query.__ash_bindings__.current_calculation_name + ) + } + }, query.__ash_bindings__.current_calculation_name} + end + defp add_calculation_selects(query, dynamics) do {in_calculations, in_body} = Enum.split_with(dynamics, fn {load, _name, _dynamic} -> is_nil(load) end) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 8fe7224c..efac6ac7 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -675,13 +675,15 @@ defmodule AshPostgres.DataLayer do @impl true def run_query(query, resource) do + query = default_bindings(query, resource) + if AshPostgres.DataLayer.Info.polymorphic?(resource) && no_table?(query) do raise_table_error!(resource, :read) else repo = dynamic_repo(resource, query) with_savepoint(repo, query, fn -> - {:ok, repo.all(query, repo_opts(nil, nil, resource))} + {:ok, repo.all(query, repo_opts(nil, nil, resource)) |> remap_mapped_fields(query)} end) end rescue @@ -916,7 +918,7 @@ defmodule AshPostgres.DataLayer do root_data, path ) do - {:ok, query} -> + {:ok, lateral_join_query} -> source_resource = path |> Enum.at(0) @@ -924,16 +926,46 @@ defmodule AshPostgres.DataLayer do |> Map.get(:resource) {:ok, - dynamic_repo(source_resource, query).all( - query, + dynamic_repo(source_resource, lateral_join_query).all( + lateral_join_query, repo_opts(nil, nil, source_resource) - )} + ) + |> remap_mapped_fields(query)} {:error, error} -> {:error, error} end end + defp remap_mapped_fields(results, query) do + calculation_names = query.__ash_bindings__.calculation_names + aggregate_names = query.__ash_bindings__.aggregate_names + + if Enum.empty?(calculation_names) and Enum.empty?(aggregate_names) do + results + else + Enum.map(results, fn result -> + result + |> remap(:calculations, calculation_names) + |> remap(:aggregates, aggregate_names) + end) + end + end + + defp remap(record, _subfield, mapping) when mapping == %{} do + record + end + + defp remap(record, subfield, mapping) do + Map.update!(record, subfield, fn subfield_values -> + Enum.reduce(mapping, subfield_values, fn {dest, source}, subfield_values -> + subfield_values + |> Map.put(dest, Map.get(subfield_values, source)) + |> Map.delete(source) + end) + end) + end + defp lateral_join_query( query, root_data, @@ -2778,7 +2810,9 @@ defmodule AshPostgres.DataLayer do parent_resources: [], aggregate_defs: %{}, current_aggregate_name: :aggregate_0, + current_calculation_name: :calculation_0, aggregate_names: %{}, + calculation_names: %{}, context: context, bindings: %{start_bindings => %{path: [], type: :root, source: resource}} }) diff --git a/lib/expr.ex b/lib/expr.ex index 268822a3..378e4948 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -19,6 +19,7 @@ defmodule AshPostgres.Expr do Lazy, Length, Now, + Round, StringJoin, StringLength, StringSplit, @@ -1170,6 +1171,31 @@ defmodule AshPostgres.Expr do end end + defp do_dynamic_expr( + query, + %Round{arguments: [num | rest], embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + _type + ) do + precision = Enum.at(rest, 0) || 1 + + frag = + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "ROUND(", + expr: num, + raw: ", ", + expr: precision, + raw: ")" + ] + } + + do_dynamic_expr(query, frag, bindings, pred_embedded? || embedded?, acc) + end + defp do_dynamic_expr( query, %Type{arguments: [arg1, arg2, constraints]}, @@ -1646,7 +1672,7 @@ defmodule AshPostgres.Expr do if is_list(other) do list_expr(query, other, bindings, true, acc, type) else - raise "Unsupported expression in AshPostgres query: #{inspect(other)}" + raise "Unsupported expression in AshPostgres query: #{inspect(other, structs: false)}" end else maybe_sanitize_list(query, other, bindings, true, acc, type) @@ -1677,7 +1703,7 @@ defmodule AshPostgres.Expr do if is_list(value) do list_expr(query, value, bindings, false, acc, type) else - raise "Unsupported expression in AshPostgres query: #{inspect(value)}" + raise "Unsupported expression in AshPostgres query: #{inspect(value, structs: false)}" end else case maybe_sanitize_list(query, value, bindings, true, acc, type) do diff --git a/lib/join.ex b/lib/join.ex index 66df64e3..d26c446c 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -198,31 +198,15 @@ defmodule AshPostgres.Join do end) end - # defp expand_join_paths(joins) do - # Enum.flat_map(joins, fn {type, path} -> - # path - # |> sub_paths() - # |> Enum.map(&add_relationship_filter_paths/1) - # end) - # end - - # defp add_relationship_filter_paths(path) do - # last = List.last(path) - # prefix = :lists.droplast(path) - - # end - - # defp sub_paths(path) do - # Enum.map(1..Enum.count(path), fn i -> - # Enum.take(path, i) - # end) - # end - def relationship_path_to_relationships(resource, path, acc \\ []) def relationship_path_to_relationships(_resource, [], acc), do: Enum.reverse(acc) - def relationship_path_to_relationships(resource, [relationship | rest], acc) do - relationship = Ash.Resource.Info.relationship(resource, relationship) + def relationship_path_to_relationships(resource, [name | rest], acc) do + relationship = Ash.Resource.Info.relationship(resource, name) + + if !relationship do + raise "no such relationship #{inspect resource}.#{name}" + end relationship_path_to_relationships(relationship.destination, rest, [relationship | acc]) end @@ -320,10 +304,10 @@ defmodule AshPostgres.Join do end |> case do {:ok, query, acc} -> - if Enum.empty?(query.joins) do + if Enum.empty?(query.joins) || is_subquery? do {:ok, query, acc} else - {:ok, subquery(query), acc} + {:ok, (from row in subquery(query), []) |> Map.put(:__ash_bindings__, query.__ash_bindings__), acc} end {:error, error} -> From 8f3100a381432f5e850d2ee1e47b1c7bc2f7b742 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Mon, 22 Jan 2024 18:32:34 +0200 Subject: [PATCH 0171/1215] fix: keep fields of `custom_index` in format that they were provided (#195) --- lib/aggregate.ex | 2 +- lib/custom_index.ex | 15 +--- lib/join.ex | 6 +- .../migration_generator.ex | 19 ++++- lib/migration_generator/operation.ex | 81 +++---------------- test/migration_generator_test.exs | 2 +- 6 files changed, 36 insertions(+), 89 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 6621de90..27ffe887 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -77,7 +77,7 @@ defmodule AshPostgres.Aggregate do first_relationship = case Ash.Resource.Info.relationship(resource, first_relationship) do nil -> - raise "No such relationship for #{inspect(first_relationship)} aggregates #{inspect aggregates}" + raise "No such relationship for #{inspect(first_relationship)} aggregates #{inspect(aggregates)}" first_relationship -> first_relationship diff --git a/lib/custom_index.ex b/lib/custom_index.ex index ec8121d2..a196c74a 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -67,20 +67,7 @@ defmodule AshPostgres.CustomIndex do def schema, do: @schema - # sobelow_skip ["DOS.StringToAtom"] - def transform(%__MODULE__{fields: fields} = index) do - index = %{ - index - | fields: - Enum.map(fields, fn field -> - if is_atom(field) do - field - else - String.to_atom(field) - end - end) - } - + def transform(index) do cond do index.name -> if Regex.match?(~r/^[0-9a-zA-Z_]+$/, index.name) do diff --git a/lib/join.ex b/lib/join.ex index d26c446c..15e0744b 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -205,7 +205,7 @@ defmodule AshPostgres.Join do relationship = Ash.Resource.Info.relationship(resource, name) if !relationship do - raise "no such relationship #{inspect resource}.#{name}" + raise "no such relationship #{inspect(resource)}.#{name}" end relationship_path_to_relationships(relationship.destination, rest, [relationship | acc]) @@ -307,7 +307,9 @@ defmodule AshPostgres.Join do if Enum.empty?(query.joins) || is_subquery? do {:ok, query, acc} else - {:ok, (from row in subquery(query), []) |> Map.put(:__ash_bindings__, query.__ash_bindings__), acc} + {:ok, + from(row in subquery(query), []) |> Map.put(:__ash_bindings__, query.__ash_bindings__), + acc} end {:error, error} -> diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 63b58489..9b77fbe5 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2840,6 +2840,17 @@ defmodule AshPostgres.MigrationGenerator do %{attribute | type: sanitize_type(attribute.type, attribute[:size])} end) end) + |> Map.update!(:custom_indexes, fn indexes -> + Enum.map(indexes, fn index -> + fields = + Enum.map(index.fields, fn + field when is_atom(field) -> %{type: "atom", value: field} + field when is_binary(field) -> %{type: "string", value: field} + end) + + %{index | fields: fields} + end) + end) |> Jason.encode!(pretty: true) end @@ -2919,7 +2930,13 @@ defmodule AshPostgres.MigrationGenerator do defp load_custom_indexes(custom_indexes) do Enum.map(custom_indexes || [], fn custom_index -> custom_index - |> Map.put_new(:fields, []) + |> Map.update(:fields, [], fn fields -> + Enum.map(fields, fn + %{type: "atom", value: field} -> String.to_atom(field) + %{type: "string", value: field} -> field + field -> field + end) + end) |> Map.put_new(:include, []) |> Map.put_new(:message, nil) |> Map.put_new(:all_tenants?, false) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index c22f381a..1dfd25a9 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -877,19 +877,11 @@ defmodule AshPostgres.MigrationGenerator.Operation do base_filter: base_filter, multitenancy: multitenancy }) do - keys = Enum.map(index.fields, &to_string/1) - keys = - if index.all_tenants? do - keys + if !index.all_tenants? and multitenancy.strategy == :attribute do + [multitenancy.attribute | index.fields] else - case multitenancy.strategy do - :attribute -> - [to_string(multitenancy.attribute) | keys] - - _ -> - keys - end + index.fields end index = @@ -921,12 +913,10 @@ defmodule AshPostgres.MigrationGenerator.Operation do index_name = AshPostgres.CustomIndex.name(table, index) keys = - case multitenancy.strategy do - :attribute -> - [to_string(multitenancy.attribute) | Enum.map(index.fields, &to_string/1)] - - _ -> - Enum.map(index.fields, &to_string/1) + if !index.all_tenants? and multitenancy.strategy == :attribute do + [multitenancy.attribute | index.fields] + else + index.fields end "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})" @@ -972,61 +962,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do defstruct [:schema, :table, :index, :base_filter, :multitenancy, no_phase: true] import Helper - def up(%{index: index, table: table, multitenancy: multitenancy, schema: schema}) do - index_name = AshPostgres.CustomIndex.name(table, index) - - keys = - case multitenancy.strategy do - :attribute -> - [to_string(multitenancy.attribute) | Enum.map(index.fields, &to_string/1)] - - _ -> - Enum.map(index.fields, &to_string/1) - end - - "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})" + def up(operation) do + AddCustomIndex.down(operation) end - def down(%{ - index: index, - table: table, - schema: schema, - base_filter: base_filter, - multitenancy: multitenancy - }) do - keys = - case multitenancy.strategy do - :attribute -> - [to_string(multitenancy.attribute) | Enum.map(index.fields, &to_string/1)] - - _ -> - Enum.map(index.fields, &to_string/1) - end - - index = - if index.where && base_filter do - %{index | where: base_filter <> " AND " <> index.where} - else - index - end - - opts = - join([ - option(:name, index.name), - option(:unique, index.unique), - option(:concurrently, index.concurrently), - option(:using, index.using), - option(:prefix, index.prefix), - option(:where, index.where), - option(:include, index.include), - option(:prefix, schema) - ]) - - if opts == "" do - "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])" - else - "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})" - end + def down(operation) do + AddCustomIndex.up(operation) end end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index ce97eb6c..1f631b29 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -294,7 +294,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file =~ ~S[@disable_ddl_transaction true] - assert file =~ ~S + assert file =~ ~S end end From 9926d5cb7c419ed0839bd328aeaeb0325d500909 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 24 Jan 2024 11:37:04 -0500 Subject: [PATCH 0172/1215] fix: only rollback to savepoint on specific errors --- lib/data_layer.ex | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index efac6ac7..76da2385 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1500,9 +1500,22 @@ defmodule AshPostgres.DataLayer do try do {:ok, fun.()} rescue - e -> - repo.query!("ROLLBACK TO #{savepoint_id}") - {:exception, e, __STACKTRACE__} + e in Postgrex.Error -> + case e do + %Postgrex.Error{ + postgres: %{ + code: :raise_exception, + message: "ash_error:" <> _, + severity: "ERROR" + } + } -> + repo.query!("ROLLBACK TO #{savepoint_id}") + # This kind of exception won't trigger + # a rollback + {:exception, e, __STACKTRACE__} + _ -> + {:exception, e, __STACKTRACE__} + end end case result do @@ -1812,7 +1825,7 @@ defmodule AshPostgres.DataLayer do exception = Module.concat([exception]) - {:error, Ash.Error.from_json(exception, input)} + {:error, :no_rollback, Ash.Error.from_json(exception, input)} end defp handle_raised_error( @@ -1832,7 +1845,7 @@ defmodule AshPostgres.DataLayer do exception = Module.concat([exception]) - {:error, Ash.Error.from_json(exception, input)} + {:error, :no_rollback, Ash.Error.from_json(exception, input)} end defp handle_raised_error( From 29da61d6611fe1a33f6787f48faa0798ef7b723f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 24 Jan 2024 12:49:38 -0500 Subject: [PATCH 0173/1215] chore: format --- lib/data_layer.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 76da2385..36166943 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1513,6 +1513,7 @@ defmodule AshPostgres.DataLayer do # This kind of exception won't trigger # a rollback {:exception, e, __STACKTRACE__} + _ -> {:exception, e, __STACKTRACE__} end From 64e117603a950bc7a28c6e211af3ec65ab8f6852 Mon Sep 17 00:00:00 2001 From: Ryan <3237598+ryanrborn@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:04:54 -0500 Subject: [PATCH 0174/1215] fix: Correct the matching used in building a distinct expression (#196) --- lib/data_layer.ex | 14 +++++++------- test/distinct_test.exs | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 36166943..d2e6f734 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2573,7 +2573,7 @@ defmodule AshPostgres.DataLayer do def distinct(query, distinct_on, resource) do case get_distinct_statement(query, distinct_on) do - {:ok, distinct_statement, query} -> + {:ok, {distinct_statement, query}} -> %{query | distinct: distinct_statement} |> apply_sort(query.__ash_bindings__[:sort], resource) @@ -2692,7 +2692,7 @@ defmodule AshPostgres.DataLayer do distinct_on, {[order_by | rest_order_by], distinct_statement, params, count, query} -> case order_by do - {^distinct_on, order} -> + {distinct_on, order} = ^distinct_on -> {distinct_expr, params, count, query} = distinct_on_expr(query, distinct_on, params, count) @@ -2710,11 +2710,11 @@ defmodule AshPostgres.DataLayer do {_, result, params, _, query} -> {:ok, - %{ - distinct - | expr: distinct.expr ++ Enum.reverse(result), - params: distinct.params ++ Enum.reverse(params) - }, query} + {%{ + distinct + | expr: distinct.expr ++ Enum.reverse(result), + params: distinct.params ++ Enum.reverse(params) + }, query}} end end end diff --git a/test/distinct_test.exs b/test/distinct_test.exs index 770f6d1e..1f779973 100644 --- a/test/distinct_test.exs +++ b/test/distinct_test.exs @@ -168,4 +168,13 @@ defmodule AshPostgres.DistinctTest do %{title: "title", negative_score: -1} ] = results end + + test "distinct used on it's own" do + results = + Post + |> Ash.Query.distinct(:title) + |> Api.read!() + + assert [_, _] = results + end end From 98b1d2ad6cc11bcba596ee3f5bd6f2548fb8fb4e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Jan 2024 10:01:43 -0500 Subject: [PATCH 0175/1215] improvement: `error_fields` for `custom_index` fix: proper return types for updates from queries fix: allow atomics to return `nil` --- lib/custom_index.ex | 29 +++++++++++++++++++++++++++++ lib/data_layer.ex | 41 +++++++++++++++++++++++++++++------------ lib/expr.ex | 9 +++++---- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index a196c74a..34db36b7 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -3,6 +3,7 @@ defmodule AshPostgres.CustomIndex do @fields [ :table, :fields, + :error_fields, :name, :unique, :concurrently, @@ -23,6 +24,10 @@ defmodule AshPostgres.CustomIndex do type: {:wrap_list, {:or, [:atom, :string]}}, doc: "The fields to include in the index." ], + error_fields: [ + type: {:list, :atom}, + doc: "The fields to attach the error to." + ], name: [ type: :string, doc: "the name of the index. Defaults to \"\#\{table\}_\#\{column\}_index\"." @@ -68,6 +73,30 @@ defmodule AshPostgres.CustomIndex do def schema, do: @schema def transform(index) do + with {:ok, index} <- set_name(index) do + set_error_fields(index) + end + end + + defp set_error_fields(index) do + if index.error_fields do + {:ok, index} + else + {:ok, %{index | error_fields: Enum.flat_map(index.fields, fn field -> + if Regex.match?(~r/^[0-9a-zA-Z_]+$/, to_string(field)) do + if is_binary(field) do + [String.to_atom(field)] + else + [field] + end + else + [] + end + end)}} + end + end + + defp set_name(index) do cond do index.name -> if Regex.match?(~r/^[0-9a-zA-Z_]+$/, index.name) do diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d2e6f734..335732f4 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1271,6 +1271,9 @@ defmodule AshPostgres.DataLayer do query end + repo = dynamic_repo(resource, changeset) + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + case query_with_atomics( resource, query, @@ -1280,16 +1283,17 @@ defmodule AshPostgres.DataLayer do [] ) do :empty -> - {:ok, changeset.data} + if options[:return_records?] do + if changeset.context[:data_layer][:use_atomic_update_data?] do + {:ok, [changeset.data]} + else + {:ok, repo.all(query)} + end + else + :ok + end {:ok, query} -> - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - - repo_opts = - Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - - repo = dynamic_repo(resource, changeset) - {_, results} = with_savepoint(repo, query, fn -> repo.update_all( @@ -2105,13 +2109,14 @@ defmodule AshPostgres.DataLayer do end defp add_unique_indexes(changeset, resource, ash_changeset) do + table = table(resource, ash_changeset) changeset = resource |> Ash.Resource.Info.identities() |> Enum.reduce(changeset, fn identity, changeset -> name = AshPostgres.DataLayer.Info.identity_index_names(resource)[identity.name] || - "#{table(resource, ash_changeset)}_#{identity.name}_index" + "#{table}_#{identity.name}_index" opts = if Map.get(identity, :message) do @@ -2127,14 +2132,26 @@ defmodule AshPostgres.DataLayer do resource |> AshPostgres.DataLayer.Info.custom_indexes() |> Enum.reduce(changeset, fn index, changeset -> + name = index.name || AshPostgres.CustomIndex.name(table, index) + opts = if index.message do - [name: index.name, message: index.message] + [name: name, message: index.message] else - [name: index.name] + [name: name] + end + + fields = + if index.error_fields do + index.error_fields + else + case Enum.filter(index.fields, &is_atom/1) do + [] -> Ash.Resource.Info.primary_key(resource) + fields -> fields + end end - Ecto.Changeset.unique_constraint(changeset, index.fields, opts) + Ecto.Changeset.unique_constraint(changeset, fields, opts) end) names = diff --git a/lib/expr.ex b/lib/expr.ex index 378e4948..92fc002b 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -39,15 +39,16 @@ defmodule AshPostgres.Expr do def dynamic_expr(query, expr, bindings, embedded? \\ false, type \\ nil, acc \\ %ExprInfo{}) + def dynamic_expr(_query, %Filter{expression: nil}, _bindings, _embedded?, _type, acc) do + # a nil filter means everything + {true, acc} + end + def dynamic_expr(query, %Filter{expression: expression}, bindings, embedded?, type, acc) do dynamic_expr(query, expression, bindings, embedded?, type, acc) end - # A nil filter means "everything" - def dynamic_expr(_, nil, _, _, _, acc), do: {true, acc} - # A true filter means "everything" def dynamic_expr(_, true, _, _, _, acc), do: {true, acc} - # A false filter means "nothing" def dynamic_expr(_, false, _, _, _, acc), do: {false, acc} def dynamic_expr(query, expression, bindings, embedded?, type, acc) do From 2b813c8975222b94ad161eed5dd34fccd0d1fdcf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Jan 2024 10:56:11 -0500 Subject: [PATCH 0176/1215] chore: consider `ignore_fields` when comparing custom indexes --- lib/migration_generator/migration_generator.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 9b77fbe5..87bfb7d7 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -357,6 +357,7 @@ defmodule AshPostgres.MigrationGenerator do end operations + |> IO.inspect() |> split_into_migrations() |> Enum.each(fn operations -> run_without_transaction? = @@ -1865,6 +1866,7 @@ defmodule AshPostgres.MigrationGenerator do Enum.map(fields, &to_string/1) end) |> add_custom_index_name(table) + |> Map.delete(:error_fields) right = right @@ -1872,6 +1874,7 @@ defmodule AshPostgres.MigrationGenerator do Enum.map(fields, &to_string/1) end) |> add_custom_index_name(table) + |> Map.delete(:error_fields) left == right end From 5952889ac7b776bd342385ba7dad7a99cef65d45 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 10:25:27 -0500 Subject: [PATCH 0177/1215] chore: remove IO.inspect --- lib/migration_generator/migration_generator.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 87bfb7d7..5033acd6 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -357,7 +357,6 @@ defmodule AshPostgres.MigrationGenerator do end operations - |> IO.inspect() |> split_into_migrations() |> Enum.each(fn operations -> run_without_transaction? = From 7b9929f07c9c3ec2ef0266b64ee7bffc5caba154 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 10:36:20 -0500 Subject: [PATCH 0178/1215] chore: format --- lib/custom_index.ex | 27 ++++++++++++++++----------- lib/data_layer.ex | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 34db36b7..2633928e 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -82,17 +82,22 @@ defmodule AshPostgres.CustomIndex do if index.error_fields do {:ok, index} else - {:ok, %{index | error_fields: Enum.flat_map(index.fields, fn field -> - if Regex.match?(~r/^[0-9a-zA-Z_]+$/, to_string(field)) do - if is_binary(field) do - [String.to_atom(field)] - else - [field] - end - else - [] - end - end)}} + {:ok, + %{ + index + | error_fields: + Enum.flat_map(index.fields, fn field -> + if Regex.match?(~r/^[0-9a-zA-Z_]+$/, to_string(field)) do + if is_binary(field) do + [String.to_atom(field)] + else + [field] + end + else + [] + end + end) + }} end end diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 335732f4..864c6ba8 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2110,6 +2110,7 @@ defmodule AshPostgres.DataLayer do defp add_unique_indexes(changeset, resource, ash_changeset) do table = table(resource, ash_changeset) + changeset = resource |> Ash.Resource.Info.identities() From 4d86fa73ad32d6a4d91c768108b83d8685717370 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 10:52:34 -0500 Subject: [PATCH 0179/1215] docs: update formatter and cheat sheet --- .formatter.exs | 1 + documentation/dsls/DSL:-AshPostgres.DataLayer.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.formatter.exs b/.formatter.exs index 934d4157..682cd1ac 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -9,6 +9,7 @@ spark_locals_without_parens = [ create?: 1, deferrable: 1, down: 1, + error_fields: 1, exclusion_constraint_names: 1, foreign_key_names: 1, identity_index_names: 1, diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 6cab440c..46b9a916 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -104,6 +104,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" | Name | Type | Default | Docs | |------|------|---------|------| +| [`error_fields`](#postgres-custom_indexes-index-error_fields){: #postgres-custom_indexes-index-error_fields } | `list(atom)` | | The fields to attach the error to. | | [`name`](#postgres-custom_indexes-index-name){: #postgres-custom_indexes-index-name } | `String.t` | | the name of the index. Defaults to "#{table}_#{column}_index". | | [`unique`](#postgres-custom_indexes-index-unique){: #postgres-custom_indexes-index-unique } | `boolean` | `false` | indicates whether the index should be unique. | | [`concurrently`](#postgres-custom_indexes-index-concurrently){: #postgres-custom_indexes-index-concurrently } | `boolean` | `false` | indicates whether the index should be created/dropped concurrently. | From 647a268183c2d19931508a935c9e5ae9033c0bd5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 15:52:08 -0500 Subject: [PATCH 0180/1215] chore: skip sobelow warning --- lib/custom_index.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 2633928e..e5a6eca3 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -72,6 +72,7 @@ defmodule AshPostgres.CustomIndex do def schema, do: @schema + # sobelow_skip ["DOS.StringToAtom"] def transform(index) do with {:ok, index} <- set_name(index) do set_error_fields(index) From a05783d422a51e0ae9c5e788a6dcbaade2bb3841 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 16:47:46 -0500 Subject: [PATCH 0181/1215] fix: only migrate/rollback one repo at a time fixes #197 --- lib/mix/tasks/ash_postgres.migrate.ex | 11 ++++------- lib/mix/tasks/ash_postgres.rollback.ex | 10 +++------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 02d14d7d..bc25fb9f 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -105,11 +105,6 @@ defmodule Mix.Tasks.AshPostgres.Migrate do repos = AshPostgres.MixHelpers.repos!(opts, args) - repo_args = - Enum.flat_map(repos, fn repo -> - ["-r", to_string(repo)] - end) - rest_opts = args |> AshPostgres.MixHelpers.delete_arg("--apis") @@ -128,7 +123,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do Mix.Task.run( "ecto.migrate", - repo_args ++ + ["-r", to_string(repo)] ++ rest_opts ++ ["--prefix", tenant, "--migrations-path", tenant_migrations_path(opts, repo)] ) @@ -141,7 +136,9 @@ defmodule Mix.Tasks.AshPostgres.Migrate do for repo <- repos do Mix.Task.run( "ecto.migrate", - repo_args ++ rest_opts ++ ["--migrations-path", migrations_path(opts, repo)] + ["-r", to_string(repo)] ++ + rest_opts ++ + ["--migrations-path", migrations_path(opts, repo) |> IO.inspect(label: "flubber")] ) Mix.Task.reenable("ecto.migrate") diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index 973d958d..ecb47657 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -64,11 +64,6 @@ defmodule Mix.Tasks.AshPostgres.Rollback do repos = AshPostgres.MixHelpers.repos!(opts, args) - repo_args = - Enum.flat_map(repos, fn repo -> - ["-r", to_string(repo)] - end) - rest_opts = args |> AshPostgres.MixHelpers.delete_arg("--apis") @@ -87,7 +82,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do Mix.Task.run( "ecto.rollback", - repo_args ++ + ["-r", to_string(repo)] ++ rest_opts ++ ["--prefix", tenant, "--migrations-path", tenant_migrations_path(opts, repo)] ) @@ -100,7 +95,8 @@ defmodule Mix.Tasks.AshPostgres.Rollback do for repo <- repos do Mix.Task.run( "ecto.rollback", - repo_args ++ rest_opts ++ ["--migrations-path", migrations_path(opts, repo)] + ["-r", to_string(repo)] ++ + rest_opts ++ ["--migrations-path", migrations_path(opts, repo)] ) Mix.Task.reenable("ecto.rollback") From b1d10c32cc68c7ea45d3b03f8af99029fabcfa60 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 16:57:04 -0500 Subject: [PATCH 0182/1215] chore: fix credo and update ash --- documentation/dsls/DSL:-AshPostgres.DataLayer.md | 12 ++++++------ lib/mix/tasks/ash_postgres.migrate.ex | 2 +- mix.exs | 3 ++- mix.lock | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 46b9a916..c451cb35 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -40,14 +40,14 @@ end |------|------|---------|------| | [`repo`](#postgres-repo){: #postgres-repo .spark-required} | `module \| (any, any -> any)` | | The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more. Can also be a function that takes a resource and a type `:read \| :mutate` and returns the repo | | [`migrate?`](#postgres-migrate?){: #postgres-migrate? } | `boolean` | `true` | Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` | -| [`migration_types`](#postgres-migration_types){: #postgres-migration_types } | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | -| [`migration_defaults`](#postgres-migration_defaults){: #postgres-migration_defaults } | `Keyword.t` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | +| [`migration_types`](#postgres-migration_types){: #postgres-migration_types } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | +| [`migration_defaults`](#postgres-migration_defaults){: #postgres-migration_defaults } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | | [`base_filter_sql`](#postgres-base_filter_sql){: #postgres-base_filter_sql } | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | | [`simple_join_first_aggregates`](#postgres-simple_join_first_aggregates){: #postgres-simple_join_first_aggregates } | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | | [`skip_unique_indexes`](#postgres-skip_unique_indexes){: #postgres-skip_unique_indexes } | `atom \| list(atom)` | `false` | Skip generating unique indexes when generating migrations | | [`unique_index_names`](#postgres-unique_index_names){: #postgres-unique_index_names } | `list({list(atom), String.t} \| {list(atom), String.t, String.t})` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | -| [`exclusion_constraint_names`](#postgres-exclusion_constraint_names){: #postgres-exclusion_constraint_names } | ``any`` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | -| [`identity_index_names`](#postgres-identity_index_names){: #postgres-identity_index_names } | ``any`` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | +| [`exclusion_constraint_names`](#postgres-exclusion_constraint_names){: #postgres-exclusion_constraint_names } | `any` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | +| [`identity_index_names`](#postgres-identity_index_names){: #postgres-identity_index_names } | `any` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | | [`foreign_key_names`](#postgres-foreign_key_names){: #postgres-foreign_key_names } | `list({atom \| String.t, String.t} \| {atom \| String.t, String.t, String.t})` | `[]` | A list of foreign keys that could raise errors, or an mfa to a function that takes a changeset and returns a list. In the format: `{:key, "name_of_constraint"}` or `{:key, "name_of_constraint", "custom error message"}` | | [`migration_ignore_attributes`](#postgres-migration_ignore_attributes){: #postgres-migration_ignore_attributes } | `list(atom)` | `[]` | A list of attributes that will be ignored when generating migrations. | | [`table`](#postgres-table){: #postgres-table } | `String.t` | | The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. | @@ -300,7 +300,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post | [`on_update`](#postgres-references-reference-on_update){: #postgres-references-reference-on_update } | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | | [`deferrable`](#postgres-references-reference-deferrable){: #postgres-references-reference-deferrable } | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | | [`name`](#postgres-references-reference-name){: #postgres-references-reference-name } | `String.t` | | The name of the foreign key to generate in the database. Defaults to
__fkey | -| [`match_with`](#postgres-references-reference-match_with){: #postgres-references-reference-match_with } | `Keyword.t` | | Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute. | +| [`match_with`](#postgres-references-reference-match_with){: #postgres-references-reference-match_with } | `keyword` | | Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute. | | [`match_type`](#postgres-references-reference-match_type){: #postgres-references-reference-match_type } | `:simple \| :partial \| :full` | | select if the match is `:simple`, `:partial`, or `:full` | @@ -362,7 +362,7 @@ check_constraint :price, "price_must_be_positive", check: "price > 0", message: | Name | Type | Default | Docs | |------|------|---------|------| -| [`attribute`](#postgres-check_constraints-check_constraint-attribute){: #postgres-check_constraints-check_constraint-attribute .spark-required} | ``any`` | | The attribute or list of attributes to which an error will be added if the check constraint fails | +| [`attribute`](#postgres-check_constraints-check_constraint-attribute){: #postgres-check_constraints-check_constraint-attribute .spark-required} | `any` | | The attribute or list of attributes to which an error will be added if the check constraint fails | | [`name`](#postgres-check_constraints-check_constraint-name){: #postgres-check_constraints-check_constraint-name .spark-required} | `String.t` | | The name of the constraint | ### Options diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index bc25fb9f..d3d3cb69 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -138,7 +138,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do "ecto.migrate", ["-r", to_string(repo)] ++ rest_opts ++ - ["--migrations-path", migrations_path(opts, repo) |> IO.inspect(label: "flubber")] + ["--migrations-path", migrations_path(opts, repo)] ) Mix.Task.reenable("ecto.migrate") diff --git a/mix.exs b/mix.exs index c8da1b0f..be48e940 100644 --- a/mix.exs +++ b/mix.exs @@ -157,7 +157,8 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.18")}, + {:ash, + ash_version(github: "ash-project/ash", ref: "f4339be4265ff4b275e9097dbe525cd7b50d8c52")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 2b8e2cf7..c596d8d1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.18.0", "498f3c1766d2343530035a3bae40dffb89eb721531c4b3375b51cdd175dfb4db", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb61da47f0e98ea6cb3dd3e3e81a91bfb4c189afbc69c44e2c4b41dea8d1f6f"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "f4339be4265ff4b275e9097dbe525cd7b50d8c52", [ref: "f4339be4265ff4b275e9097dbe525cd7b50d8c52"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, @@ -13,7 +13,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, + "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, @@ -37,7 +37,7 @@ "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, - "spark": {:hex, :spark, "1.1.53", "db8a374ef6ada4f38389386bec76b2fa6331d4755308a6e359acad16472e29ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "5f8a8e2b4abd2544517bb8d29c28576239254b5979d66d9781b154706c4199dd"}, + "spark": {:hex, :spark, "1.1.54", "54dac39403a2960f738ba5d60678d20b30de7381fb51b787b6bcb6aeabb73d9d", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "abc9a67cfb60a97d2f3c7e270fa968a2ace94f389e2741d406239d237ec6dbb1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From 4930a69b950059dfb36e3af37d291fb4bcc21416 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Jan 2024 17:17:32 -0500 Subject: [PATCH 0183/1215] improvement: support `count_nils` expression --- lib/expr.ex | 26 ++ .../test_repo/posts/20240129221511.json | 328 ++++++++++++++++++ .../20240129221511_migrate_resources15.exs | 21 ++ test/calculation_test.exs | 17 + test/support/resources/post.ex | 5 + 5 files changed, 397 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20240129221511.json create mode 100644 priv/test_repo/migrations/20240129221511_migrate_resources15.exs diff --git a/lib/expr.ex b/lib/expr.ex index 92fc002b..923be404 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -10,6 +10,7 @@ defmodule AshPostgres.Expr do At, CompositeType, Contains, + CountNils, DateAdd, DateTimeAdd, Error, @@ -373,6 +374,31 @@ defmodule AshPostgres.Expr do end end + defp do_dynamic_expr( + query, + %CountNils{arguments: [list], embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + type + ) do + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "(SELECT COUNT(*) FROM unnest(", + expr: list, + raw: ") AS item WHERE item IS NULL)" + ] + }, + bindings, + embedded?, + acc, + type + ) + end + defp do_dynamic_expr( query, %Contains{arguments: [left, right], embedded?: pred_embedded?}, diff --git a/priv/resource_snapshots/test_repo/posts/20240129221511.json b/priv/resource_snapshots/test_repo/posts/20240129221511.json new file mode 100644 index 00000000..5739a790 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240129221511.json @@ -0,0 +1,328 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "750AEDC8C41D03DB043C207300801B6D4543AA1E17F98A14E89FED6610EA2967", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "where": null, + "unique": true, + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240129221511_migrate_resources15.exs b/priv/test_repo/migrations/20240129221511_migrate_resources15.exs new file mode 100644 index 00000000..3ee35d6b --- /dev/null +++ b/priv/test_repo/migrations/20240129221511_migrate_resources15.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources15 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(:posts) do + add :list_containing_nils, {:array, :text} + end + end + + def down do + alter table(:posts) do + remove :list_containing_nils + end + end +end \ No newline at end of file diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 9058f01a..f24ef19c 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -534,6 +534,23 @@ defmodule AshPostgres.CalculationTest do end end + describe "count_nils/1" do + test "counts nil values" do + Post + |> Ash.Changeset.new(%{list_containing_nils: ["a", nil, "b", nil, "c"]}) + |> Api.create!() + + Post + |> Ash.Changeset.new(%{list_containing_nils: ["a", nil, "b", "c"]}) + |> Api.create!() + + assert [_] = + Post + |> Ash.Query.filter(count_nils(list_containing_nils) == 2) + |> Api.read!() + end + end + describe "-/1" do test "makes numbers negative" do Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d21d4ebc..691430f8 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -101,6 +101,11 @@ defmodule AshPostgres.Test.Post do attribute(:uniq_two, :string) attribute(:uniq_custom_one, :string) attribute(:uniq_custom_two, :string) + + attribute :list_containing_nils, {:array, :string} do + constraints(nil_items?: true) + end + create_timestamp(:created_at) update_timestamp(:updated_at) end From aad5f9a1823f1bfc2dbf64713110b70e3c398e39 Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Wed, 31 Jan 2024 00:26:39 +1000 Subject: [PATCH 0184/1215] test: add failing test to demonstrate bug with manual updates (#198) --- test/manual_update_test.exs | 17 +++++++++++++++++ test/support/resources/post.ex | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 test/manual_update_test.exs diff --git a/test/manual_update_test.exs b/test/manual_update_test.exs new file mode 100644 index 00000000..39c32043 --- /dev/null +++ b/test/manual_update_test.exs @@ -0,0 +1,17 @@ +defmodule AshPostgres.ManualUpdateTest do + use AshPostgres.RepoCase, async: false + + test "Manual update defined in a module to update an attribute" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.new(%{title: "match"}) + |> AshPostgres.Test.Api.create!() + + post = + post + |> Ash.Changeset.for_update(:manual_update) + |> AshPostgres.Test.Api.update!() + + assert post.title == "manual" + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 691430f8..e1335aeb 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -72,6 +72,10 @@ defmodule AshPostgres.Test.Post do argument(:amount, :integer, default: 1) change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) end + + update :manual_update do + manual(AshPostgres.Test.Post.ManualUpdate) + end end identities do @@ -438,3 +442,17 @@ defmodule CalculatePostPriceStringWithSymbol do end) end end + +defmodule AshPostgres.Test.Post.ManualUpdate do + use Ash.Resource.ManualUpdate + + def update(changeset, _opts, _context) do + { + :ok, + changeset.data + |> Ash.Changeset.for_update(:update, changeset.attributes) + |> Ash.Changeset.force_change_attribute(:title, "manual") + |> AshPostgres.Test.Api.update!() + } + end +end From ffafa6c9f5a42890e8a17c599944f56ce6b32d89 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jan 2024 12:49:52 -0500 Subject: [PATCH 0185/1215] improvement: support `Ash.Changeset.OriginalDataNotAvailable` --- lib/data_layer.ex | 8 +++++++- mix.exs | 2 +- mix.lock | 2 +- test/manual_update_test.exs | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 864c6ba8..9a958a32 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1253,7 +1253,13 @@ defmodule AshPostgres.DataLayer do @impl true def update_query(query, changeset, resource, options) do ecto_changeset = - changeset.data + case changeset.data do + %Ash.Changeset.OriginalDataNotAvailable{} -> + changeset.resource.__struct__ + + data -> + data + end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> ecto_changeset(changeset, :update, true, true) diff --git a/mix.exs b/mix.exs index be48e940..5fdec8c1 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "f4339be4265ff4b275e9097dbe525cd7b50d8c52")}, + ash_version(github: "ash-project/ash", ref: "9e02b0d0c7cad6be4bd84ca58f3452aa8ab7f0c3")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index c596d8d1..86056dd2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "f4339be4265ff4b275e9097dbe525cd7b50d8c52", [ref: "f4339be4265ff4b275e9097dbe525cd7b50d8c52"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "9e02b0d0c7cad6be4bd84ca58f3452aa8ab7f0c3", [ref: "9e02b0d0c7cad6be4bd84ca58f3452aa8ab7f0c3"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/manual_update_test.exs b/test/manual_update_test.exs index 39c32043..753224b8 100644 --- a/test/manual_update_test.exs +++ b/test/manual_update_test.exs @@ -1,5 +1,5 @@ defmodule AshPostgres.ManualUpdateTest do - use AshPostgres.RepoCase, async: false + use AshPostgres.RepoCase, async: true test "Manual update defined in a module to update an attribute" do post = From fbd41407c37c2457643c20afa7f6d011a0125414 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 30 Jan 2024 19:17:40 +0100 Subject: [PATCH 0186/1215] test: add test that shows subquery error (#199) --- .../subquery_access/20240130133933.json | 67 ++++++++++++++++ .../subquery_child/20240130133933.json | 39 +++++++++ .../subquery_parent/20240130133933.json | 59 ++++++++++++++ .../subquery_through/20240130133933.json | 57 +++++++++++++ ...133933_add_resources_for_subquery_test.exs | 79 +++++++++++++++++++ test/subquery_test.exs | 19 +++++ test/support/resources/subquery/access.ex | 54 +++++++++++++ test/support/resources/subquery/child.ex | 53 +++++++++++++ test/support/resources/subquery/child_api.ex | 11 +++ test/support/resources/subquery/parent.ex | 62 +++++++++++++++ test/support/resources/subquery/parent_api.ex | 11 +++ test/support/resources/subquery/through.ex | 57 +++++++++++++ 12 files changed, 568 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/subquery_access/20240130133933.json create mode 100644 priv/resource_snapshots/test_repo/subquery_child/20240130133933.json create mode 100644 priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json create mode 100644 priv/resource_snapshots/test_repo/subquery_through/20240130133933.json create mode 100644 priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs create mode 100644 test/subquery_test.exs create mode 100644 test/support/resources/subquery/access.ex create mode 100644 test/support/resources/subquery/child.ex create mode 100644 test/support/resources/subquery/child_api.ex create mode 100644 test/support/resources/subquery/parent.ex create mode 100644 test/support/resources/subquery/parent_api.ex create mode 100644 test/support/resources/subquery/through.ex diff --git a/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json b/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json new file mode 100644 index 00000000..63dc281c --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json @@ -0,0 +1,67 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "parent_id", + "references": { + "name": "subquery_access_parent_id_fkey", + "table": "subquery_parent", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "email", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "subquery_access", + "hash": "B7DFB67DE808049D03358E207644E9865B03C3C7707F6FE1FA6F581605046CFF", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json b/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json new file mode 100644 index 00000000..b7ba6b33 --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json @@ -0,0 +1,39 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "state", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "subquery_child", + "hash": "14EB56AF533B090146C1A6FA930E83747D6D2765EE518576A4D6329B5CFA2B9E", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json b/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json new file mode 100644 index 00000000..9ac0d750 --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "owner_email", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "other_owner_email", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "visible", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "subquery_parent", + "hash": "4F7C9D2564C1C54AB4E45BE8A5209B764EDC05F060C9B9D3E8EBE8A134FAF797", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json b/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json new file mode 100644 index 00000000..c8c4a2bf --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json @@ -0,0 +1,57 @@ +{ + "attributes": [ + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "parent_id", + "references": { + "name": "subquery_through_parent_id_fkey", + "table": "subquery_parent", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "child_id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + } + ], + "table": "subquery_through", + "hash": "D6357C195FC4B8B9227D3BC0E5F31FD0038ACA5D5951DA14104C3F157F520177", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs b/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs new file mode 100644 index 00000000..f54f17a0 --- /dev/null +++ b/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs @@ -0,0 +1,79 @@ +defmodule AshPostgres.TestRepo.Migrations.AddResourcesForSubqueryTest 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 + create table(:subquery_through, primary_key: false) do + add :parent_id, :uuid, null: false, primary_key: true + add :child_id, :uuid, null: false, primary_key: true + end + + create table(:subquery_parent, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + end + + alter table(:subquery_through) do + modify :parent_id, + references(:subquery_parent, + column: :id, + name: "subquery_through_parent_id_fkey", + type: :uuid, + prefix: "public" + ) + end + + alter table(:subquery_parent) do + add :owner_email, :text + add :other_owner_email, :text + add :visible, :boolean + end + + create table(:subquery_child, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + add :state, :text + end + + create table(:subquery_access, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + + add :parent_id, + references(:subquery_parent, + column: :id, + name: "subquery_access_parent_id_fkey", + type: :uuid, + prefix: "public" + ) + + add :email, :text + end + end + + def down do + drop constraint(:subquery_access, "subquery_access_parent_id_fkey") + + drop table(:subquery_access) + + drop table(:subquery_child) + + alter table(:subquery_parent) do + remove :visible + remove :other_owner_email + remove :owner_email + end + + drop constraint(:subquery_through, "subquery_through_parent_id_fkey") + + alter table(:subquery_through) do + modify :parent_id, :uuid + end + + drop table(:subquery_parent) + + drop table(:subquery_through) + end +end diff --git a/test/subquery_test.exs b/test/subquery_test.exs new file mode 100644 index 00000000..50964a61 --- /dev/null +++ b/test/subquery_test.exs @@ -0,0 +1,19 @@ +defmodule AshPostgres.SubqueryTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.Subquery.{Access, Child, Parent, Through} + + test "joins are wrapped correctly wrapped in subqueries" do + {:ok, child} = Child.create(%{}) + + {:ok, parent} = + Parent.create(%{visible: true}) + + Access.create(%{parent_id: parent.id, email: "foo@bar.com"}) + + Through.create(%{parent_id: parent.id, child_id: child.id}) + + assert {:ok, _} = + Child.read(actor: %{email: "foo@bar.com"}) + end +end diff --git a/test/support/resources/subquery/access.ex b/test/support/resources/subquery/access.ex new file mode 100644 index 00000000..cc585e92 --- /dev/null +++ b/test/support/resources/subquery/access.ex @@ -0,0 +1,54 @@ +defmodule AshPostgres.Test.Subquery.Access do + @moduledoc false + alias AshPostgres.Test.Subquery.Parent + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] + + require Ash.Query + + postgres do + repo AshPostgres.TestRepo + table "subquery_access" + end + + attributes do + uuid_primary_key(:id) + attribute(:parent_id, :uuid) + attribute(:email, :string) + end + + code_interface do + define_for(AshPostgres.Test.Subquery.ParentApi) + + define(:create) + define(:read) + end + + relationships do + belongs_to(:parent, Parent) + end + + policies do + policy always() do + authorize_if(always()) + end + end + + actions do + defaults([:create, :update, :destroy]) + + read :read do + primary?(true) + + prepare(fn query, %{actor: actor} -> + # THIS CAUSES THE ERROR + query + |> Ash.Query.filter(parent.visible == true) + end) + end + end +end diff --git a/test/support/resources/subquery/child.ex b/test/support/resources/subquery/child.ex new file mode 100644 index 00000000..a85500de --- /dev/null +++ b/test/support/resources/subquery/child.ex @@ -0,0 +1,53 @@ +defmodule AshPostgres.Test.Subquery.Child do + @moduledoc false + alias AshPostgres.Test.Subquery.Through + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] + + postgres do + repo AshPostgres.TestRepo + table "subquery_child" + end + + attributes do + uuid_primary_key(:id) + attribute(:state, :string) + end + + code_interface do + define_for(AshPostgres.Test.Subquery.ChildApi) + + define(:create) + define(:read) + end + + relationships do + has_many :throughs, Through do + source_attribute(:id) + destination_attribute(:child_id) + end + end + + policies do + policy [ + action(:read), + expr( + (not is_nil(^actor(:email)) and + (exists(throughs.parent, owner_email == ^actor(:email)) or + exists(throughs.parent, other_owner_email == ^actor(:email)) or + exists(throughs.parent.accesses, email == ^actor(:email)))) or + state in ["public", "open"] + ) + ] do + authorize_if(always()) + end + end + + actions do + defaults([:create, :read, :update, :destroy]) + end +end diff --git a/test/support/resources/subquery/child_api.ex b/test/support/resources/subquery/child_api.ex new file mode 100644 index 00000000..320dc709 --- /dev/null +++ b/test/support/resources/subquery/child_api.ex @@ -0,0 +1,11 @@ +defmodule AshPostgres.Test.Subquery.ChildApi do + @moduledoc false + alias AshPostgres.Test.Subquery.Child + alias AshPostgres.Test.Subquery.Through + use Ash.Api + + resources do + resource(Child) + resource(Through) + end +end diff --git a/test/support/resources/subquery/parent.ex b/test/support/resources/subquery/parent.ex new file mode 100644 index 00000000..33421c32 --- /dev/null +++ b/test/support/resources/subquery/parent.ex @@ -0,0 +1,62 @@ +defmodule AshPostgres.Test.Subquery.Parent do + @moduledoc false + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] + + alias AshPostgres.Test.Subquery.{Access, Child, Through} + + postgres do + repo AshPostgres.TestRepo + table "subquery_parent" + end + + attributes do + uuid_primary_key(:id) + attribute(:owner_email, :string) + attribute(:other_owner_email, :string) + attribute(:visible, :boolean) + end + + relationships do + many_to_many :children, Child do + through(Through) + source_attribute(:id) + source_attribute_on_join_resource(:parent_id) + destination_attribute(:id) + destination_attribute_on_join_resource(:child_id) + api(AshPostgres.Test.Subquery.ChildApi) + end + + has_many(:accesses, Access) + end + + policies do + policy [ + action(:read), + expr( + visible == true and + (not is_nil(^actor(:email)) and + (owner_email == ^actor(:email) or other_owner_email == ^actor(:email) or + exists(accesses, email == ^actor(:email)))) + ) + ] do + authorize_if(always()) + end + end + + code_interface do + define_for(AshPostgres.Test.Subquery.ParentApi) + + define(:create) + define(:read) + + define(:get_by_id, action: :read, get_by: :id) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end +end diff --git a/test/support/resources/subquery/parent_api.ex b/test/support/resources/subquery/parent_api.ex new file mode 100644 index 00000000..0bb99db0 --- /dev/null +++ b/test/support/resources/subquery/parent_api.ex @@ -0,0 +1,11 @@ +defmodule AshPostgres.Test.Subquery.ParentApi do + @moduledoc false + alias AshPostgres.Test.Subquery.Access + alias AshPostgres.Test.Subquery.Parent + use Ash.Api + + resources do + resource(Parent) + resource(Access) + end +end diff --git a/test/support/resources/subquery/through.ex b/test/support/resources/subquery/through.ex new file mode 100644 index 00000000..cc1cfe01 --- /dev/null +++ b/test/support/resources/subquery/through.ex @@ -0,0 +1,57 @@ +defmodule AshPostgres.Test.Subquery.Through do + @moduledoc false + alias AshPostgres.Test.Subquery.Child + alias AshPostgres.Test.Subquery.Parent + alias AshPostgres.Test.Subquery.ParentApi + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] + + postgres do + repo AshPostgres.TestRepo + table "subquery_through" + end + + attributes do + attribute :parent_id, :uuid do + primary_key?(true) + allow_nil?(false) + end + + attribute :child_id, :uuid do + primary_key?(true) + allow_nil?(false) + end + end + + code_interface do + define_for(AshPostgres.Test.Subquery.ChildApi) + + define(:create) + define(:read) + end + + relationships do + belongs_to :parent, Parent do + api(ParentApi) + end + + belongs_to :child, Child do + source_attribute(:parent_id) + destination_attribute(:id) + end + end + + policies do + policy always() do + authorize_if(always()) + end + end + + actions do + defaults([:create, :read, :update, :destroy]) + end +end From 8c43deea9bc5fbbd58f2635943d744c20e4aa816 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jan 2024 15:21:59 -0500 Subject: [PATCH 0187/1215] fix: properly build subqueries when required for relationship queries --- lib/join.ex | 27 ++++++++++++++++----------- test/subquery_test.exs | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 15e0744b..8c02a883 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -293,6 +293,21 @@ defmodule AshPostgres.Join do is_subquery? ) + query = + if Enum.empty?(List.wrap(query.order_bys)) && Enum.empty?(query.joins) do + query + else + from(row in subquery(query), []) + |> AshPostgres.DataLayer.default_bindings(relationship.destination) + |> AshPostgres.DataLayer.merge_expr_accumulator( + query.__ash_bindings__.expression_accumulator + ) + |> Map.update!( + :__ash_bindings__, + &Map.put(&1, :current, query.__ash_bindings__.current) + ) + end + {:ok, Map.put(query, :__tenant__, Map.get(root_query, :__tenant__)), acc} {:error, error} -> @@ -323,22 +338,12 @@ defmodule AshPostgres.Join do true ) when sort not in [nil, []] do - query = - if query.aliases[0] do - query - else - from(row in query, as: ^0) - end - query = AshPostgres.DataLayer.default_bindings(query, destination) {:ok, order_by, query} = AshPostgres.Sort.sort(query, sort, query.__ash_bindings__.resource, [], 0, :return) - from(row in subquery(Ecto.Query.order_by(query, ^order_by)), []) - |> AshPostgres.DataLayer.default_bindings(destination) - |> AshPostgres.DataLayer.merge_expr_accumulator(query.__ash_bindings__.expression_accumulator) - |> Map.update!(:__ash_bindings__, &Map.put(&1, :current, query.__ash_bindings__.current)) + from(row in query, order_by: ^order_by) end defp do_relationship_sort(query, _, _), do: query diff --git a/test/subquery_test.exs b/test/subquery_test.exs index 50964a61..4b12a80d 100644 --- a/test/subquery_test.exs +++ b/test/subquery_test.exs @@ -3,7 +3,7 @@ defmodule AshPostgres.SubqueryTest do alias AshPostgres.Test.Subquery.{Access, Child, Parent, Through} - test "joins are wrapped correctly wrapped in subqueries" do + test "joins are correctly wrapped in subqueries" do {:ok, child} = Child.create(%{}) {:ok, parent} = From 82dd14b953457a7f6048017bbf3e310a3bc60ed2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jan 2024 15:32:50 -0500 Subject: [PATCH 0188/1215] chore: credo --- test/support/resources/post.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e1335aeb..f97d1d2d 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -444,6 +444,7 @@ defmodule CalculatePostPriceStringWithSymbol do end defmodule AshPostgres.Test.Post.ManualUpdate do + @moduledoc false use Ash.Resource.ManualUpdate def update(changeset, _opts, _context) do From 5006a195bdc1a4e7bb1f301865128dded17652cd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jan 2024 16:39:17 -0500 Subject: [PATCH 0189/1215] test: add test reproducing ash core fix for expression calculations --- test/calculation_test.exs | 15 +++++++++++++++ test/support/resources/author.ex | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index f24ef19c..dcbc9f74 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -690,6 +690,21 @@ defmodule AshPostgres.CalculationTest do |> Api.read_one!() end + test "an expression calculation that loads a runtime calculation works" do + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Bill", + last_name: "Jones", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Api.create!() + + assert [%{expr_referencing_runtime: "Bill Jones Bill Jones"}] = + Author + |> Ash.Query.load(:expr_referencing_runtime) + |> Api.read!() + end + test "lazy values are evaluated lazily" do Author |> Ash.Changeset.for_create(:create, %{ diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 8757d4e7..215bea3b 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -3,6 +3,16 @@ defmodule AshPostgres.Test.Author do use Ash.Resource, data_layer: AshPostgres.DataLayer + defmodule RuntimeFullName do + use Ash.Calculation + + def calculate(records, _, _) do + Enum.map(records, fn record -> + record.first_name <> " " <> record.last_name + end) + end + end + postgres do table("authors") repo(AshPostgres.TestRepo) @@ -47,6 +57,14 @@ defmodule AshPostgres.Test.Author do calculate(:title, :string, expr(bio[:title])) calculate(:full_name, :string, expr(first_name <> " " <> last_name)) + calculate(:runtime_full_name, :string, RuntimeFullName) + + calculate( + :expr_referencing_runtime, + :string, + expr(runtime_full_name <> " " <> runtime_full_name) + ) + calculate(:full_name_with_nils, :string, expr(string_join([first_name, last_name], " "))) calculate(:full_name_with_nils_no_joiner, :string, expr(string_join([first_name, last_name]))) calculate(:split_full_name, {:array, :string}, expr(string_split(full_name))) From 745a3b8264e3f8df31f8cb205f9f7373ac4c0a43 Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Wed, 31 Jan 2024 11:04:42 +1000 Subject: [PATCH 0190/1215] test: add failing test to demo bug with changeset loading related data (#200) --- test/manual_update_test.exs | 10 ++++++++++ test/support/resources/post.ex | 1 + 2 files changed, 11 insertions(+) diff --git a/test/manual_update_test.exs b/test/manual_update_test.exs index 753224b8..c41445e1 100644 --- a/test/manual_update_test.exs +++ b/test/manual_update_test.exs @@ -7,11 +7,21 @@ defmodule AshPostgres.ManualUpdateTest do |> Ash.Changeset.new(%{title: "match"}) |> AshPostgres.Test.Api.create!() + AshPostgres.Test.Comment + |> Ash.Changeset.new(%{title: "_"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> AshPostgres.Test.Api.create!() + post = post |> Ash.Changeset.for_update(:manual_update) |> AshPostgres.Test.Api.update!() assert post.title == "manual" + + # The manual update has a call to Ash.Changeset.load that should + # cause the comments to be loaded + assert Ash.Resource.loaded?(post, :comments) + assert Enum.count(post.comments) == 1 end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index f97d1d2d..bffae755 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -453,6 +453,7 @@ defmodule AshPostgres.Test.Post.ManualUpdate do changeset.data |> Ash.Changeset.for_update(:update, changeset.attributes) |> Ash.Changeset.force_change_attribute(:title, "manual") + |> Ash.Changeset.load(:comments) |> AshPostgres.Test.Api.update!() } end From 6ceac063f8cd2d007931813447ba7fad47cf761f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jan 2024 20:55:13 -0500 Subject: [PATCH 0191/1215] chore: credo --- test/support/resources/author.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 215bea3b..eee71074 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -4,6 +4,7 @@ defmodule AshPostgres.Test.Author do data_layer: AshPostgres.DataLayer defmodule RuntimeFullName do + @moduledoc false use Ash.Calculation def calculate(records, _, _) do From e956e7ead231139382a8c0df447505490935f267 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jan 2024 21:07:17 -0500 Subject: [PATCH 0192/1215] chore: update ash --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 5fdec8c1..b48921f1 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "9e02b0d0c7cad6be4bd84ca58f3452aa8ab7f0c3")}, + ash_version(github: "ash-project/ash", ref: "7811dfaa1165404ec6e7dccb6f3b2e0c93065292")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 86056dd2..f16b40aa 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "9e02b0d0c7cad6be4bd84ca58f3452aa8ab7f0c3", [ref: "9e02b0d0c7cad6be4bd84ca58f3452aa8ab7f0c3"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "7811dfaa1165404ec6e7dccb6f3b2e0c93065292", [ref: "7811dfaa1165404ec6e7dccb6f3b2e0c93065292"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From d71cb8179278b23a4718791813e9da9137504e06 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 31 Jan 2024 16:30:23 -0500 Subject: [PATCH 0193/1215] improvement: handle if select is present on query --- lib/data_layer.ex | 1 + mix.exs | 2 +- mix.lock | 2 +- test/bulk_update_test.exs | 19 +++++++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 9a958a32..54f2d590 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1267,6 +1267,7 @@ defmodule AshPostgres.DataLayer do query = query |> default_bindings(resource, changeset.context) + |> Ecto.Query.exclude(:select) query = if options[:return_records?] do diff --git a/mix.exs b/mix.exs index b48921f1..29b8aa4e 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "7811dfaa1165404ec6e7dccb6f3b2e0c93065292")}, + ash_version(github: "ash-project/ash", ref: "3b3e3a06f26a1cb43d69e1e470a462c37fb84987")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index f16b40aa..ef80e962 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "7811dfaa1165404ec6e7dccb6f3b2e0c93065292", [ref: "7811dfaa1165404ec6e7dccb6f3b2e0c93065292"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "3b3e3a06f26a1cb43d69e1e470a462c37fb84987", [ref: "3b3e3a06f26a1cb43d69e1e470a462c37fb84987"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 200c8f97..9495f590 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -35,4 +35,23 @@ defmodule AshPostgres.BulkUpdateTest do assert titles == ["fred_stuff", "george"] end + + test "bulk updates can be done even on stream inputs" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Api.read!() + |> Api.bulk_update!(:update, %{}, + atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}, + return_records?: true + ) + + titles = + Post + |> Api.read!() + |> Enum.map(& &1.title) + |> Enum.sort() + + assert titles == ["fred_stuff", "george_stuff"] + end end From b018262637a2ef7b3981ac4ca36df36695673b10 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 1 Feb 2024 13:09:06 -0500 Subject: [PATCH 0194/1215] test: add new test verifying batch destroy behavior chore: add sobelow warning skip --- lib/custom_index.ex | 2 +- mix.exs | 2 +- test/bulk_destroy_test.exs | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index e5a6eca3..3f6fbfad 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -72,13 +72,13 @@ defmodule AshPostgres.CustomIndex do def schema, do: @schema - # sobelow_skip ["DOS.StringToAtom"] def transform(index) do with {:ok, index} <- set_name(index) do set_error_fields(index) end end + # sobelow_skip ["DOS.StringToAtom"] defp set_error_fields(index) do if index.error_fields do {:ok, index} diff --git a/mix.exs b/mix.exs index 29b8aa4e..022de8b2 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "3b3e3a06f26a1cb43d69e1e470a462c37fb84987")}, + ash_version(github: "ash-project/ash", ref: "57654d3df49d39fe727bd86df3a0496c8598c7c1")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index 8d0e0c6c..5327a379 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -17,7 +17,7 @@ defmodule AshPostgres.BulkDestroyTest do assert Api.read!(Post) == [] end - test "bulk updates only apply to things that the query produces" do + test "bulk destroys only apply to things that the query produces" do Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post @@ -27,4 +27,14 @@ defmodule AshPostgres.BulkDestroyTest do # 😢 sad assert [%{title: "george"}] = Api.read!(Post) end + + test "bulk destroys can be done even on stream inputs" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Api.read!() + |> Api.bulk_destroy!(:destroy, %{}) + + assert [] = Api.read!(Post) + end end From a940022eba92ebcc3bb3554d9b3f5b4bbf44ffaf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 1 Feb 2024 14:59:49 -0500 Subject: [PATCH 0195/1215] fix: ensure identity keys is never missing --- lib/data_layer.ex | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 54f2d590..d0242fe7 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2117,6 +2117,7 @@ defmodule AshPostgres.DataLayer do defp add_unique_indexes(changeset, resource, ash_changeset) do table = table(resource, ash_changeset) + pkey = Ash.Resource.Info.primary_key(changeset.resource) changeset = resource @@ -2133,7 +2134,15 @@ defmodule AshPostgres.DataLayer do [name: name] end - Ecto.Changeset.unique_constraint(changeset, identity.keys, opts) + case identity.keys do + [] -> + pkey + + keys -> + keys + end + + Ecto.Changeset.unique_constraint(changeset, keys, opts) end) changeset = From de7d6feaa3ca254b5bf05b8a5e448f432d814171 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 1 Feb 2024 15:12:17 -0500 Subject: [PATCH 0196/1215] =?UTF-8?q?fix:=20forgot=20to=20bind=20keys=20to?= =?UTF-8?q?=20a=20variable=20=F0=9F=A4=A6=F0=9F=8F=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data_layer.ex | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d0242fe7..d091c0f3 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2134,13 +2134,14 @@ defmodule AshPostgres.DataLayer do [name: name] end - case identity.keys do - [] -> - pkey + keys = + case identity.keys do + [] -> + pkey - keys -> - keys - end + keys -> + keys + end Ecto.Changeset.unique_constraint(changeset, keys, opts) end) From 4494bf971ff0c9385c9b443687601bb8e5931e5b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 1 Feb 2024 15:21:50 -0500 Subject: [PATCH 0197/1215] chore: `changeset.resource` -> `resource` --- lib/data_layer.ex | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d091c0f3..312d770c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2117,7 +2117,7 @@ defmodule AshPostgres.DataLayer do defp add_unique_indexes(changeset, resource, ash_changeset) do table = table(resource, ash_changeset) - pkey = Ash.Resource.Info.primary_key(changeset.resource) + pkey = Ash.Resource.Info.primary_key(resource) changeset = resource diff --git a/mix.lock b/mix.lock index ef80e962..0cad94fc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "3b3e3a06f26a1cb43d69e1e470a462c37fb84987", [ref: "3b3e3a06f26a1cb43d69e1e470a462c37fb84987"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "57654d3df49d39fe727bd86df3a0496c8598c7c1", [ref: "57654d3df49d39fe727bd86df3a0496c8598c7c1"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From 43b576e09d534ed5365e789d53dcdd25b5f244cc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 1 Feb 2024 15:48:23 -0500 Subject: [PATCH 0198/1215] fix: use pkey if error fields is empty --- lib/data_layer.ex | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 312d770c..e001bd30 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2134,7 +2134,7 @@ defmodule AshPostgres.DataLayer do [name: name] end - keys = + fields = case identity.keys do [] -> pkey @@ -2143,7 +2143,7 @@ defmodule AshPostgres.DataLayer do keys end - Ecto.Changeset.unique_constraint(changeset, keys, opts) + Ecto.Changeset.unique_constraint(changeset, fields, opts) end) changeset = @@ -2161,10 +2161,13 @@ defmodule AshPostgres.DataLayer do fields = if index.error_fields do - index.error_fields + case index.error_fields do + [] -> pkey + fields -> fields + end else case Enum.filter(index.fields, &is_atom/1) do - [] -> Ash.Resource.Info.primary_key(resource) + [] -> pkey fields -> fields end end From a8b7f3b3e7a59e695812caa255750f98f9323750 Mon Sep 17 00:00:00 2001 From: "Eduardo B. A" <279828+sezaru@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:25:02 -0300 Subject: [PATCH 0199/1215] feat: Make MigrationGenerator accept atoms (#201) Co-authored-by: Eduardo B. Alexandre --- .../migration_generator.ex | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 5033acd6..0b462e5d 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2909,7 +2909,7 @@ defmodule AshPostgres.MigrationGenerator do |> Map.update!(:custom_statements, &load_custom_statements/1) |> Map.put_new(:check_constraints, []) |> Map.update!(:check_constraints, &load_check_constraints/1) - |> Map.update!(:repo, &String.to_atom/1) + |> Map.update!(:repo, &maybe_to_atom/1) |> Map.put_new(:multitenancy, %{ attribute: nil, strategy: nil, @@ -2924,7 +2924,7 @@ defmodule AshPostgres.MigrationGenerator do Map.update!(constraint, :attribute, fn attribute -> attribute |> List.wrap() - |> Enum.map(&String.to_atom/1) + |> Enum.map(&maybe_to_atom/1) end) end) end @@ -2934,7 +2934,7 @@ defmodule AshPostgres.MigrationGenerator do custom_index |> Map.update(:fields, [], fn fields -> Enum.map(fields, fn - %{type: "atom", value: field} -> String.to_atom(field) + %{type: "atom", value: field} -> maybe_to_atom(field) %{type: "string", value: field} -> field field -> field end) @@ -2947,14 +2947,14 @@ defmodule AshPostgres.MigrationGenerator do defp load_custom_statements(statements) do Enum.map(statements || [], fn statement -> - Map.update!(statement, :name, &String.to_atom/1) + Map.update!(statement, :name, &maybe_to_atom/1) end) end defp load_multitenancy(multitenancy) do multitenancy - |> Map.update!(:strategy, fn strategy -> strategy && String.to_atom(strategy) end) - |> Map.update!(:attribute, fn attribute -> attribute && String.to_atom(attribute) end) + |> Map.update!(:strategy, fn strategy -> strategy && maybe_to_atom(strategy) end) + |> Map.update!(:attribute, fn attribute -> attribute && maybe_to_atom(attribute) end) end defp load_attribute(attribute, table) do @@ -2977,9 +2977,9 @@ defmodule AshPostgres.MigrationGenerator do attribute = if Map.has_key?(attribute, :name) do - Map.put(attribute, :source, String.to_atom(attribute.name)) + Map.put(attribute, :source, maybe_to_atom(attribute.name)) else - Map.update!(attribute, :source, &String.to_atom/1) + Map.update!(attribute, :source, &maybe_to_atom/1) end attribute @@ -3000,7 +3000,7 @@ defmodule AshPostgres.MigrationGenerator do ) |> Map.delete(:ignore) |> rewrite(:ignore?, :ignore) - |> Map.update!(:destination_attribute, &String.to_atom/1) + |> Map.update!(:destination_attribute, &maybe_to_atom/1) |> Map.put_new(:deferrable, false) |> Map.update!(:deferrable, fn "initially" -> :initially @@ -3011,15 +3011,15 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put_new(:destination_attribute_generated, false) |> Map.put_new(:on_delete, nil) |> Map.put_new(:on_update, nil) - |> Map.update!(:on_delete, &(&1 && String.to_atom(&1))) - |> Map.update!(:on_update, &(&1 && String.to_atom(&1))) + |> Map.update!(:on_delete, &(&1 && maybe_to_atom(&1))) + |> Map.update!(:on_update, &(&1 && maybe_to_atom(&1))) |> Map.put_new(:match_with, nil) |> Map.put_new(:match_type, nil) |> Map.update!( :match_with, - &(&1 && Enum.into(&1, %{}, fn {k, v} -> {String.to_atom(k), String.to_atom(v)} end)) + &(&1 && Enum.into(&1, %{}, fn {k, v} -> {maybe_to_atom(k), maybe_to_atom(v)} end)) ) - |> Map.update!(:match_type, &(&1 && String.to_atom(&1))) + |> Map.update!(:match_type, &(&1 && maybe_to_atom(&1))) |> Map.put( :name, Map.get(references, :name) || "#{table}_#{attribute.source}_fkey" @@ -3075,15 +3075,15 @@ defmodule AshPostgres.MigrationGenerator do end defp load_type(type) do - String.to_atom(type) + maybe_to_atom(type) end defp load_identity(identity, table) do identity - |> Map.update!(:name, &String.to_atom/1) + |> Map.update!(:name, &maybe_to_atom/1) |> Map.update!(:keys, fn keys -> keys - |> Enum.map(&String.to_atom/1) + |> Enum.map(&maybe_to_atom/1) |> Enum.sort() end) |> add_index_name(table) @@ -3094,4 +3094,7 @@ defmodule AshPostgres.MigrationGenerator do defp add_index_name(%{name: name} = index, table) do Map.put_new(index, :index_name, "#{table}_#{name}_unique_index") end + + defp maybe_to_atom(value) when is_atom(value), do: value + defp maybe_to_atom(value), do: String.to_atom(value) end From 809cd5582f257b7c63b56f0a26c6c37ad0bd9330 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 4 Feb 2024 10:12:39 -0500 Subject: [PATCH 0200/1215] improvement: don't drop primary key in case of removal --- .../migration_generator.ex | 49 +++++++++++++++---- lib/migration_generator/operation.ex | 18 +++++-- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 0b462e5d..4f34fa70 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1652,7 +1652,7 @@ defmodule AshPostgres.MigrationGenerator do defp do_fetch_operations(snapshot, old_snapshot, opts, acc) do attribute_operations = attribute_operations(snapshot, old_snapshot, opts) - pkey_operations = pkey_operations(snapshot, old_snapshot, attribute_operations) + pkey_operations = pkey_operations(snapshot, old_snapshot, attribute_operations, opts) rewrite_all_identities? = changing_multitenancy_affects_identities?(snapshot, old_snapshot) @@ -1889,7 +1889,7 @@ defmodule AshPostgres.MigrationGenerator do ) end - defp pkey_operations(snapshot, old_snapshot, attribute_operations) do + defp pkey_operations(snapshot, old_snapshot, attribute_operations, opts) do if old_snapshot[:empty?] do [] else @@ -1914,14 +1914,45 @@ defmodule AshPostgres.MigrationGenerator do end ) - if must_drop_pkey? do - [ + drop_in_down? = + Enum.any?(attribute_operations, fn + %Operation.AlterAttribute{ + new_attribute: %{primary_key?: true} + } -> + true + + %Operation.AddAttribute{ + attribute: %{primary_key?: true} + } -> + true + + _ -> + false + end) + + drop_in_down_commented? = + Enum.any?(attribute_operations, fn + %Operation.RemoveAttribute{ + commented?: true, + attribute: %{primary_key?: true} + } -> + true + + _ -> + false + end) + + [ + must_drop_pkey? && %Operation.RemovePrimaryKey{schema: snapshot.schema, table: snapshot.table}, - %Operation.RemovePrimaryKeyDown{schema: snapshot.schema, table: snapshot.table} - ] - else - [] - end + must_drop_pkey? && drop_in_down? && + %Operation.RemovePrimaryKeyDown{ + commented?: !opts.drop_columns && drop_in_down_commented?, + schema: snapshot.schema, + table: snapshot.table + } + ] + |> Enum.filter(& &1) end end diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 1dfd25a9..1a15846c 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -942,17 +942,27 @@ defmodule AshPostgres.MigrationGenerator.Operation do defmodule RemovePrimaryKeyDown do @moduledoc false - defstruct [:schema, :table, no_phase: true] + defstruct [:schema, :table, commented?: false, no_phase: true] def up(_) do "" end - def down(%{schema: schema, table: table}) do + def down(%{schema: schema, table: table, commented?: commented?}) do + comment = + if commented? do + """ + # Primary key removal is dropped because a corresponding attribute removal + # has been commented out. If you uncomment this, uncomment the attribute removal and vice versa. + """ + else + "" + end + if schema do - "drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: \"#{schema}\")" + "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: \"#{schema}\")" else - "drop constraint(#{inspect(table)}, \"#{table}_pkey\")" + "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\")" end end end From b2b05618b50918e98e6e0b57a6422fe531a0e587 Mon Sep 17 00:00:00 2001 From: Kilian Cirera Sant Date: Mon, 5 Feb 2024 16:52:09 -0500 Subject: [PATCH 0201/1215] improvement: Include modules in installed_extensions return type (#202) As per the documentation (and indeed the actual behavior): > Extensions can be a string, representing a standard postgres extension, or a module that implements `AshPostgres.CustomExtension`. --- lib/repo.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/repo.ex b/lib/repo.ex index 3cd42c71..01b6ceb3 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -42,7 +42,7 @@ defmodule AshPostgres.Repo do """ @doc "Use this to inform the data layer about what extensions are installed" - @callback installed_extensions() :: [String.t()] + @callback installed_extensions() :: [String.t() | module()] @doc """ Use this to inform the data layer about the oldest potential postgres version it will be run on. From 3f7bdb8fc16adae3b9fe34b86549c2cc7b739f10 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 6 Feb 2024 16:04:35 +0100 Subject: [PATCH 0202/1215] test: add test for parent in relationship filter (#203) --- test/rel_with_parent_filter_test.exs | 52 ++++++++++++++++++++++++++++ test/support/resources/author.ex | 8 +++++ 2 files changed, 60 insertions(+) create mode 100644 test/rel_with_parent_filter_test.exs diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs new file mode 100644 index 00000000..78afa752 --- /dev/null +++ b/test/rel_with_parent_filter_test.exs @@ -0,0 +1,52 @@ +defmodule AshPostgres.RelWithParentFilterTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.{Api, Author} + + require Ash.Query + + test "filter on relationship using parent works as expected when used in aggregate" do + %{id: author_id} = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Api.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John"}) + |> Api.create!() + + Logger.configure(level: :debug) + + # here we get the expected result of 1 because it is done in the same query + assert %{num_of_authors_with_same_first_name: 1} = + Author + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(id == ^author_id) + |> Ash.Query.load(:num_of_authors_with_same_first_name) + |> Api.read_one!() + end + + test "filter on relationship using parent works as expected when loading relationship" do + %{id: author_id} = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Api.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John"}) + |> Api.create!() + + assert %{authors_with_same_first_name: authors} = + Author + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(id == ^author_id) + # right now it first loads the contact + # then it loads the relationship + # but when doing that it does a inner lateral join + # instead of using the id from the parent relationship + |> Ash.Query.load(:authors_with_same_first_name) + |> Api.read_one!() + + assert length(authors) == 1 + end +end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index eee71074..98d35691 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -34,6 +34,12 @@ defmodule AshPostgres.Test.Author do relationships do has_one(:profile, AshPostgres.Test.Profile) has_many(:posts, AshPostgres.Test.Post) + + has_many :authors_with_same_first_name, __MODULE__ do + source_attribute(:first_name) + destination_attribute(:first_name) + filter(expr(parent(id) != id)) + end end aggregates do @@ -122,5 +128,7 @@ defmodule AshPostgres.Test.Author do count :count_of_posts_with_better_comment, [:posts, :comments] do join_filter([:posts, :comments], expr(parent(score) < likes)) end + + count(:num_of_authors_with_same_first_name, :authors_with_same_first_name) end end From c72b57ece54d252f22d36e96108f7f4187760cf7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 6 Feb 2024 12:39:16 -0500 Subject: [PATCH 0203/1215] fix: use primary key of source as join key --- lib/data_layer.ex | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e001bd30..f418a71b 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -925,12 +925,14 @@ defmodule AshPostgres.DataLayer do |> elem(0) |> Map.get(:resource) - {:ok, - dynamic_repo(source_resource, lateral_join_query).all( - lateral_join_query, - repo_opts(nil, nil, source_resource) - ) - |> remap_mapped_fields(query)} + results = + dynamic_repo(source_resource, lateral_join_query).all( + lateral_join_query, + repo_opts(nil, nil, source_resource) + ) + |> remap_mapped_fields(query) + + {:ok, results} {:error, error} -> {:error, error} @@ -1010,6 +1012,8 @@ defmodule AshPostgres.DataLayer do |> set_subquery_prefix(source_query, relationship.destination) |> subquery() + source_pkey = Ash.Resource.Info.primary_key(source_query.resource) + source_query.resource |> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]}) |> Ash.Query.set_tenant(source_query.tenant) @@ -1037,7 +1041,7 @@ defmodule AshPostgres.DataLayer do on: true, order_by: destination.__order__, select: destination, - select_merge: %{__lateral_join_source__: field(source, ^source_attribute)}, + select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, distinct: true )} else @@ -1046,7 +1050,7 @@ defmodule AshPostgres.DataLayer do inner_lateral_join: destination in ^subquery, on: true, select: destination, - select_merge: %{__lateral_join_source__: field(source, ^source_attribute)}, + select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, distinct: true )} end @@ -1067,6 +1071,7 @@ defmodule AshPostgres.DataLayer do ) do source_query = Ash.Query.new(source_query) source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) + source_pkey = Ash.Resource.Info.primary_key(source_query.resource) through_resource |> Ash.Query.new() @@ -1128,10 +1133,7 @@ defmodule AshPostgres.DataLayer do field( parent_as(^through_query.__ash_bindings__.current), ^source_attribute - ), - select_merge: %{ - __lateral_join_source__: field(through, ^source_attribute_on_join_resource) - } + ) ) |> set_subquery_prefix( source_query, @@ -1145,6 +1147,7 @@ defmodule AshPostgres.DataLayer do inner_lateral_join: destination in ^subquery, on: true, select: destination, + select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, order_by: destination.__order__, distinct: true )} @@ -1168,10 +1171,7 @@ defmodule AshPostgres.DataLayer do field( parent_as(^through_query.__ash_bindings__.current), ^source_attribute - ), - select_merge: %{ - __lateral_join_source__: field(through, ^source_attribute_on_join_resource) - } + ) ) |> set_subquery_prefix( source_query, @@ -1185,6 +1185,7 @@ defmodule AshPostgres.DataLayer do inner_lateral_join: destination in ^subquery, on: true, select: destination, + select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, distinct: true )} end From 1ea360d15c0fb57af8a6f4949576e89729e6fd3e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 7 Feb 2024 14:39:38 -0500 Subject: [PATCH 0204/1215] improvement: detect bigserial when altering attributes closes #204 --- lib/migration_generator/operation.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 1a15846c..2a2b6f13 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -63,6 +63,10 @@ defmodule AshPostgres.MigrationGenerator.Operation do :bigint end + def reference_type(%{type: :integer, default: "nil", generated?: true}, _) do + ":bigserial" + end + def reference_type(%{type: type}, _) do type end @@ -487,7 +491,11 @@ defmodule AshPostgres.MigrationGenerator.Operation do Map.get(old_attribute, :references) != Map.get(attribute, :references) do reference(multitenancy, attribute, schema) else - inspect(attribute.type) + if attribute.type == :biging and attribute.default == "nil" and attribute.generated? do + ":bigserial" + else + inspect(attribute.type) + end end "modify #{inspect(attribute.source)}, #{type_or_reference}#{alter_opts(attribute, old_attribute)}" From a9d38e91e1d45fdf3e178c2153c4f1be1503244d Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 7 Feb 2024 20:45:34 +0100 Subject: [PATCH 0205/1215] Improvement: mark (i)like functions as predicates (#205) --- lib/functions/ilike.ex | 2 +- lib/functions/like.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/functions/ilike.ex b/lib/functions/ilike.ex index 82e7cf0d..8fa403d4 100644 --- a/lib/functions/ilike.ex +++ b/lib/functions/ilike.ex @@ -3,7 +3,7 @@ defmodule AshPostgres.Functions.ILike do Maps to the builtin postgres function `ilike`. """ - use Ash.Query.Function, name: :ilike + use Ash.Query.Function, name: :ilike, predicate?: true def args, do: [[:string, :string]] end diff --git a/lib/functions/like.ex b/lib/functions/like.ex index bfd2bbd8..8b2d51b0 100644 --- a/lib/functions/like.ex +++ b/lib/functions/like.ex @@ -3,7 +3,7 @@ defmodule AshPostgres.Functions.Like do Maps to the builtin postgres function `like`. """ - use Ash.Query.Function, name: :like + use Ash.Query.Function, name: :like, predicate?: true def args, do: [[:string, :string]] end From 03b630396c63857416f91a3efb6812968b1d1c0b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 10 Feb 2024 18:30:48 -0500 Subject: [PATCH 0206/1215] test: update tests to demonstrate streaming update --- test/bulk_update_test.exs | 12 ++++++++++++ test/rel_with_parent_filter_test.exs | 2 -- test/support/resources/post.ex | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 9495f590..3a229929 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -54,4 +54,16 @@ defmodule AshPostgres.BulkUpdateTest do assert titles == ["fred_stuff", "george_stuff"] end + + test "bulk updates that require initial data must use streaming" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.Query.for_read(:paginated, authorize?: true) + |> Api.bulk_update!(:requires_initial_data, %{}, + authorize?: true, + allow_stream_with: :full_read, + authorize_query?: false + ) + end end diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs index 78afa752..4ac3aa7e 100644 --- a/test/rel_with_parent_filter_test.exs +++ b/test/rel_with_parent_filter_test.exs @@ -15,8 +15,6 @@ defmodule AshPostgres.RelWithParentFilterTest do |> Ash.Changeset.for_create(:create, %{first_name: "John"}) |> Api.create!() - Logger.configure(level: :debug) - # here we get the expected result of 1 because it is done in the same query assert %{num_of_authors_with_same_first_name: 1} = Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index bffae755..7bb987e2 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1,3 +1,17 @@ +defmodule PassIfOriginalDataPresent do + use Ash.Policy.SimpleCheck + + def describe(_options), do: "original data present" + + def match?(_, _, _) do + true + end + + def requires_original_data?(_, _) do + true + end +end + defmodule AshPostgres.Test.Post do @moduledoc false use Ash.Resource, @@ -15,6 +29,10 @@ defmodule AshPostgres.Test.Post do policy action(:allow_any) do authorize_if(always()) end + + policy action(:requires_initial_data) do + authorize_if(PassIfOriginalDataPresent) + end end postgres do @@ -73,6 +91,11 @@ defmodule AshPostgres.Test.Post do change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) end + update :requires_initial_data do + argument(:amount, :integer, default: 1) + change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) + end + update :manual_update do manual(AshPostgres.Test.Post.ManualUpdate) end From 930d16a5e9e5585df44bc14e809adb1e0a780795 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Feb 2024 10:08:48 -0500 Subject: [PATCH 0207/1215] fix: handle original data not available in destroy_query --- lib/data_layer.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index f418a71b..3adca1db 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1329,7 +1329,13 @@ defmodule AshPostgres.DataLayer do @impl true def destroy_query(query, changeset, resource, options) do ecto_changeset = - changeset.data + case changeset.data do + %Ash.Changeset.OriginalDataNotAvailable{} -> + changeset.resource.__struct__ + + data -> + data + end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> ecto_changeset(changeset, :update, true, true) From db3efe15ce354f34368d78575985bf70c241d524 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Feb 2024 10:22:32 -0500 Subject: [PATCH 0208/1215] chore: update ash --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 022de8b2..ffd5c704 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "57654d3df49d39fe727bd86df3a0496c8598c7c1")}, + ash_version(github: "ash-project/ash", ref: "80fc8b0896f6ef59a26c220009cd20d6042ad7d2")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 0cad94fc..228dd227 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "57654d3df49d39fe727bd86df3a0496c8598c7c1", [ref: "57654d3df49d39fe727bd86df3a0496c8598c7c1"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "80fc8b0896f6ef59a26c220009cd20d6042ad7d2", [ref: "80fc8b0896f6ef59a26c220009cd20d6042ad7d2"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From 6d0a29f45c722b32b49edbb3701041d26d7734c5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Feb 2024 10:24:59 -0500 Subject: [PATCH 0209/1215] chore: credo --- test/support/resources/post.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 7bb987e2..63696500 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1,4 +1,5 @@ defmodule PassIfOriginalDataPresent do + @moduledoc false use Ash.Policy.SimpleCheck def describe(_options), do: "original data present" From be15759de05ee069d6778bb384c3c80416c87ab8 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 14 Feb 2024 16:25:25 +0100 Subject: [PATCH 0210/1215] test: add test to show problem with field policies in filters (#206) --- test/filter_field_policy_test.exs | 63 ++++++++++++++++++++++++++ test/support/resources/organization.ex | 17 ++++++- test/support/resources/post.ex | 6 +++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 test/filter_field_policy_test.exs diff --git a/test/filter_field_policy_test.exs b/test/filter_field_policy_test.exs new file mode 100644 index 00000000..4275d685 --- /dev/null +++ b/test/filter_field_policy_test.exs @@ -0,0 +1,63 @@ +defmodule FilterFieldPolicyTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.{Api, Post, Organization, User} + + require Ash.Query + + setup do + # I needed this to make the query actually fail + # this triggers an exeption in the reporting of + # the policy breakdowns because the policies are + # nil + + # Without this it would just run the query which + # might be even worse as the policies are not applied + # correctly and I guess `nil` is a special case for + # field policies because you can have no polcies + # and that is still valid? + current_level = Logger.level() + current_setting = Application.get_env(:ash, :policies) + + Application.put_env( + :ash, + :policies, + Keyword.merge(current_setting |> List.wrap(), log_policy_breakdowns: current_level) + ) + + on_exit(fn -> + Application.put_env(:ash, :policies, current_setting) + end) + end + + test "filter uses the correct field policies when exanding refs" do + organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "test_org"}) + |> Api.create!() + + User + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) + |> Api.create!() + + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) + |> Api.create!() + + filter = Ash.Filter.parse_input!(Post, %{organization: %{name: %{ilike: "%org"}}}) + + assert [_] = + Post + |> Ash.Query.do_filter(filter) + |> Ash.Query.for_read(:allow_any) + |> Api.read!(actor: %{id: "test"}) + + filter = Ash.Filter.parse_input!(Post, %{organization: %{users: %{name: %{ilike: "%bar"}}}}) + + assert [_] = + Post + |> Ash.Query.do_filter(filter) + |> Ash.Query.for_read(:allow_any) + |> Api.read!(actor: %{id: "test"}) + end +end diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index f23e783a..ada81e25 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -1,13 +1,28 @@ defmodule AshPostgres.Test.Organization do @moduledoc false use Ash.Resource, - data_layer: AshPostgres.DataLayer + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] postgres do table("orgs") repo(AshPostgres.TestRepo) end + policies do + policy always() do + authorize_if(always()) + end + end + + field_policies do + field_policy :* do + authorize_if(always()) + end + end + actions do defaults([:create, :read, :update, :destroy]) end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 63696500..3b9e7afd 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -36,6 +36,12 @@ defmodule AshPostgres.Test.Post do end end + field_policies do + field_policy :* do + authorize_if(always()) + end + end + postgres do table("posts") repo(AshPostgres.TestRepo) From 3146d5f97d5dc46c4357edb5b9581618be299cf5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 14 Feb 2024 11:04:06 -0500 Subject: [PATCH 0211/1215] chore: update ash, add tests for field policies --- mix.exs | 2 +- test/filter_field_policy_test.exs | 25 ------------------------- test/support/resources/user.ex | 5 ++++- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/mix.exs b/mix.exs index ffd5c704..1200703d 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "80fc8b0896f6ef59a26c220009cd20d6042ad7d2")}, + ash_version(github: "ash-project/ash", ref: "e9d2d8c575f700ced88ed563fc015af8061f0df0")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/test/filter_field_policy_test.exs b/test/filter_field_policy_test.exs index 4275d685..f292becd 100644 --- a/test/filter_field_policy_test.exs +++ b/test/filter_field_policy_test.exs @@ -5,31 +5,6 @@ defmodule FilterFieldPolicyTest do require Ash.Query - setup do - # I needed this to make the query actually fail - # this triggers an exeption in the reporting of - # the policy breakdowns because the policies are - # nil - - # Without this it would just run the query which - # might be even worse as the policies are not applied - # correctly and I guess `nil` is a special case for - # field policies because you can have no polcies - # and that is still valid? - current_level = Logger.level() - current_setting = Application.get_env(:ash, :policies) - - Application.put_env( - :ash, - :policies, - Keyword.merge(current_setting |> List.wrap(), log_policy_breakdowns: current_level) - ) - - on_exit(fn -> - Application.put_env(:ash, :policies, current_setting) - end) - end - test "filter uses the correct field policies when exanding refs" do organization = Organization diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index 31647d77..442a9c2e 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -18,7 +18,10 @@ defmodule AshPostgres.Test.User do end relationships do - belongs_to(:organization, AshPostgres.Test.Organization) + belongs_to :organization, AshPostgres.Test.Organization do + attribute_writable?(true) + end + has_many(:accounts, AshPostgres.Test.Account) end end From 8f10c9b8242de630d82d1be689f351e58bc8d860 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 14 Feb 2024 12:12:54 -0500 Subject: [PATCH 0212/1215] chore: update credo --- lib/migration_generator/migration_generator.ex | 3 +-- mix.lock | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 4f34fa70..9e12ce0f 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -504,8 +504,7 @@ defmodule AshPostgres.MigrationGenerator do count_with_create = snapshots - |> Enum.filter(& &1.has_create_action) - |> Enum.count() + |> Enum.count(& &1.has_create_action) new_snapshot = %{ snapshot diff --git a/mix.lock b/mix.lock index 228dd227..0e3aaf2f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,14 @@ %{ "ash": {:git, "https://github.com/ash-project/ash.git", "80fc8b0896f6ef59a26c220009cd20d6042ad7d2", [ref: "80fc8b0896f6ef59a26c220009cd20d6042ad7d2"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, - "credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"}, + "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, @@ -19,7 +19,7 @@ "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.5.5", "4f8369f3c9347e06a7f289de98fadfc95194149156335c5292479a53eddbccd2", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3b1e3b12968f9da6f79b5e2b2274477206949376e3579d05a5f3d439eda0b746"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, From 7a4fe87561a800f330f4aa313d16353231efa412 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 14 Feb 2024 13:56:58 -0500 Subject: [PATCH 0213/1215] chore: update ash, add tests --- mix.exs | 2 +- test/ash_postgres_test.exs | 12 ++++++++++++ test/support/resources/post.ex | 4 ++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 1200703d..d3b73f45 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "e9d2d8c575f700ced88ed563fc015af8061f0df0")}, + ash_version(github: "ash-project/ash", ref: "f23f0a29fec8a231e3fbff64034be8c9efb87e73")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 5c58bda7..a0d25ace 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -11,4 +11,16 @@ defmodule AshPostgresTest do metadata: %{action: :create, actor: nil, resource: AshPostgres.Test.Post} } end + + test "filter policies are are applied" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.new(%{title: "good"}) + |> AshPostgres.Test.Api.create!() + + post + |> Ash.Changeset.for_update(:update, %{title: "bad"}, authorize?: true) + |> AshPostgres.Test.Api.update!() + |> Map.get(:title) + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 3b9e7afd..9a12c649 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -34,6 +34,10 @@ defmodule AshPostgres.Test.Post do policy action(:requires_initial_data) do authorize_if(PassIfOriginalDataPresent) end + + policy action_type(:update) do + authorize_unless(changing_attributes(title: [from: "good", to: "bad"])) + end end field_policies do From 256b4d0b9a354c4ba09f17cc6f97ff7fe5a98dd9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 14 Feb 2024 17:54:28 -0500 Subject: [PATCH 0214/1215] test: add test for filter policy on bulk actions --- mix.exs | 2 +- test/ash_postgres_test.exs | 9 ++++++++- test/filter_field_policy_test.exs | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index d3b73f45..19567f10 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "f23f0a29fec8a231e3fbff64034be8c9efb87e73")}, + ash_version(github: "ash-project/ash", ref: "dcbccec7c36e2cad9d8366fa797b617e043b7b28")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index a0d25ace..736caae9 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -18,8 +18,15 @@ defmodule AshPostgresTest do |> Ash.Changeset.new(%{title: "good"}) |> AshPostgres.Test.Api.create!() + assert_raise Ash.Error.Forbidden, fn -> + post + |> Ash.Changeset.for_update(:update, %{title: "bad"}, authorize?: true) + |> AshPostgres.Test.Api.update!() + |> Map.get(:title) + end + post - |> Ash.Changeset.for_update(:update, %{title: "bad"}, authorize?: true) + |> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true) |> AshPostgres.Test.Api.update!() |> Map.get(:title) end diff --git a/test/filter_field_policy_test.exs b/test/filter_field_policy_test.exs index f292becd..35b29e5e 100644 --- a/test/filter_field_policy_test.exs +++ b/test/filter_field_policy_test.exs @@ -1,7 +1,7 @@ defmodule FilterFieldPolicyTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post, Organization, User} + alias AshPostgres.Test.{Api, Organization, Post, User} require Ash.Query From 2d18c9cbebc49c850fe0aa4a8bb74773effef094 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 09:23:48 -0500 Subject: [PATCH 0215/1215] fix: properly alter renaming attributes in migration generator fixes #207 --- .../migration_generator.ex | 23 ++++++++++++-- mix.lock | 2 +- test/migration_generator_test.exs | 30 +++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 9e12ce0f..210dd761 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1975,8 +1975,24 @@ defmodule AshPostgres.MigrationGenerator do {attribute, Enum.find( old_snapshot.attributes, - &(&1.source == attribute.source && - attributes_unequal?(&1, attribute, snapshot.repo, old_snapshot, snapshot)) + fn old_attribute -> + source_match = + Enum.find_value(attributes_to_rename, old_attribute.source, fn {new, old} -> + if old.source == old_attribute.source do + new.source + end + end) + + source_match == + attribute.source && + attributes_unequal?( + old_attribute, + attribute, + snapshot.repo, + old_snapshot, + snapshot + ) + end )} end) |> Enum.filter(&elem(&1, 1)) @@ -2343,7 +2359,8 @@ defmodule AshPostgres.MigrationGenerator do get_new_attribute(adding) end - {adding -- [new_attribute], [], [{new_attribute, removing}]} + {Enum.reject(adding, &(&1.source == new_attribute.source)), [], + [{new_attribute, removing}]} else {adding, [removing], []} end diff --git a/mix.lock b/mix.lock index 0e3aaf2f..778de981 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "80fc8b0896f6ef59a26c220009cd20d6042ad7d2", [ref: "80fc8b0896f6ef59a26c220009cd20d6042ad7d2"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "dcbccec7c36e2cad9d8366fa797b617e043b7b28", [ref: "dcbccec7c36e2cad9d8366fa797b617e043b7b28"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 1f631b29..e9f571d9 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -358,6 +358,36 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(file2) =~ ~S[rename table(:posts, prefix: "example"), :title, to: :name] end + + test "renaming a field honors additional changes" do + defposts do + postgres do + schema("example") + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, allow_nil?: false, default: "fred") + end + end + + defapi([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Api, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + + assert File.read!(file2) =~ ~S[rename table(:posts, prefix: "example"), :title, to: :name] + assert File.read!(file2) =~ ~S[modify :title, :text, null: true, default: nil] + end end describe "creating follow up migrations" do From 3fd5a7a90738f1616e414cbb65e73a63b33b5bd5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 10:54:20 -0500 Subject: [PATCH 0216/1215] test: add some tests for atomics --- test/ash_postgres_test.exs | 7 +++++-- test/support/resources/post.ex | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 736caae9..df0fc921 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -12,7 +12,7 @@ defmodule AshPostgresTest do } end - test "filter policies are are applied" do + test "filter policies are applied" do post = AshPostgres.Test.Post |> Ash.Changeset.new(%{title: "good"}) @@ -20,7 +20,10 @@ defmodule AshPostgresTest do assert_raise Ash.Error.Forbidden, fn -> post - |> Ash.Changeset.for_update(:update, %{title: "bad"}, authorize?: true) + |> Ash.Changeset.for_update(:update, %{title: "bad"}, + authorize?: true, + actor: %{id: Ash.UUID.generate()} + ) |> AshPostgres.Test.Api.update!() |> Map.get(:title) end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 9a12c649..12c509d8 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -36,6 +36,7 @@ defmodule AshPostgres.Test.Post do end policy action_type(:update) do + authorize_if(relates_to_actor_via([:author, :authors_with_same_first_name])) authorize_unless(changing_attributes(title: [from: "good", to: "bad"])) end end From db3634eb2bcab3bb5e416c9c040587bdad108a5e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 10:55:14 -0500 Subject: [PATCH 0217/1215] chore: update ash --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 19567f10..56fcfd8f 100644 --- a/mix.exs +++ b/mix.exs @@ -158,7 +158,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, {:ash, - ash_version(github: "ash-project/ash", ref: "dcbccec7c36e2cad9d8366fa797b617e043b7b28")}, + ash_version(github: "ash-project/ash", ref: "52e0fb8ba6226ae204db45b8cb5bffb2f6afb506")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, From fd746a784c849c33f2d88178bf0988b4aba7b96c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 11:56:37 -0500 Subject: [PATCH 0218/1215] fix: properly set 0 binding on joined subquery creation fixes #208 --- lib/join.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/join.ex b/lib/join.ex index 8c02a883..3b0f9705 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -297,7 +297,7 @@ defmodule AshPostgres.Join do if Enum.empty?(List.wrap(query.order_bys)) && Enum.empty?(query.joins) do query else - from(row in subquery(query), []) + from(row in subquery(query), as: ^0) |> AshPostgres.DataLayer.default_bindings(relationship.destination) |> AshPostgres.DataLayer.merge_expr_accumulator( query.__ash_bindings__.expression_accumulator From b53a98dbb3c98bd51beadd1662fd11102804bd25 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 12:15:02 -0500 Subject: [PATCH 0219/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 778de981..72d72f06 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "dcbccec7c36e2cad9d8366fa797b617e043b7b28", [ref: "dcbccec7c36e2cad9d8366fa797b617e043b7b28"]}, + "ash": {:git, "https://github.com/ash-project/ash.git", "52e0fb8ba6226ae204db45b8cb5bffb2f6afb506", [ref: "52e0fb8ba6226ae204db45b8cb5bffb2f6afb506"]}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From bdcf97a638837779032f28fa55389e156bbd9a69 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 22:58:11 -0500 Subject: [PATCH 0220/1215] fix: avoid double wrapping in subqueries --- lib/join.ex | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 3b0f9705..e1f24892 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -317,19 +317,6 @@ defmodule AshPostgres.Join do query -> {:error, query} end - |> case do - {:ok, query, acc} -> - if Enum.empty?(query.joins) || is_subquery? do - {:ok, query, acc} - else - {:ok, - from(row in subquery(query), []) |> Map.put(:__ash_bindings__, query.__ash_bindings__), - acc} - end - - {:error, error} -> - {:error, error} - end end defp do_relationship_sort( @@ -640,7 +627,11 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(from(row in relationship_destination, limit: 1)) + if Map.get(relationship, :from_many?) do + subquery(from(row in relationship_destination, limit: 1)) + else + subquery(relationship_destination) + end else relationship_destination end @@ -785,11 +776,11 @@ defmodule AshPostgres.Join do end) needs_subquery? = - used_aggregates != [] || Map.get(relationship, :from_many?) + used_aggregates != [] relationship_destination = if needs_subquery? do - subquery(from(row in relationship_destination, limit: 1)) + subquery(relationship_destination) else relationship_destination end @@ -870,7 +861,9 @@ defmodule AshPostgres.Join do used_aggregates = Ash.Filter.used_aggregates(filter, full_path) use_root_query_bindings? = Enum.empty?(used_aggregates) - needs_subquery? = Map.get(relationship, :from_many?, false) + needs_subquery? = + Map.get(relationship, :from_many?, false) || + (relationship.cardinality == :many && !Enum.empty?(used_aggregates)) root_bindings = if use_root_query_bindings? && !needs_subquery? do @@ -901,7 +894,11 @@ defmodule AshPostgres.Join do relationship_destination = if needs_subquery? do - subquery(from(row in relationship_destination, limit: 1)) + if Map.get(relationship, :from_many?) do + subquery(from(row in relationship_destination, limit: 1)) + else + subquery(relationship_destination) + end else relationship_destination end From cde117e18b2380ad3b59a632ce73c042ec0b0204 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 23:06:24 -0500 Subject: [PATCH 0221/1215] fix: don't subquery if we need to reference `parent_as` --- lib/join.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/join.ex b/lib/join.ex index e1f24892..12ab0481 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -294,7 +294,8 @@ defmodule AshPostgres.Join do ) query = - if Enum.empty?(List.wrap(query.order_bys)) && Enum.empty?(query.joins) do + if !is_subquery? || + (Enum.empty?(List.wrap(query.order_bys)) && Enum.empty?(query.joins)) do query else from(row in subquery(query), as: ^0) From 6cb53dc8d70e2b7406a0f62e8f8fca388f42a367 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Feb 2024 23:11:44 -0500 Subject: [PATCH 0222/1215] fix: allow subquerying a `through` while aggregating a many to many --- lib/aggregate.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 27ffe887..7f433d9e 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -587,7 +587,7 @@ defmodule AshPostgres.Aggregate do [join_relationship], nil, subquery.__ash_bindings__.current, - true, + false, true ) From 27fcdfc8fd5c5bbe433edc598a8c1b047108bad0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 16 Feb 2024 16:12:23 -0500 Subject: [PATCH 0223/1215] improvement: update to latest ash --- mix.exs | 3 +-- mix.lock | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 56fcfd8f..4119dd1f 100644 --- a/mix.exs +++ b/mix.exs @@ -157,8 +157,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, - ash_version(github: "ash-project/ash", ref: "52e0fb8ba6226ae204db45b8cb5bffb2f6afb506")}, + {:ash, ash_version("~> 2.19")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 72d72f06..acec0723 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "52e0fb8ba6226ae204db45b8cb5bffb2f6afb506", [ref: "52e0fb8ba6226ae204db45b8cb5bffb2f6afb506"]}, + "ash": {:hex, :ash, "2.19.0", "9235e1efbe82b4468849416c95d43e7c2e20c3f0dfdc5413598baf275b0c7465", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "eea848622156ab294cdde6da012f0902c69d44c3df63f5ecd92b66eb3c4b3db3"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, From 542d4981bb080a8658af80bb11e3b1e0f5112b1a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 16 Feb 2024 16:12:31 -0500 Subject: [PATCH 0224/1215] chore: release version v1.5.0 --- CHANGELOG.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4611455e..339c0019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,79 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.0](https://github.com/ash-project/ash_postgres/compare/v1.4.0...v1.5.0) (2024-02-16) + + + + +### Features: + +* Make MigrationGenerator accept atoms (#201) + +### Bug Fixes: + +* allow subquerying a `through` while aggregating a many to many + +* don't subquery if we need to reference `parent_as` + +* avoid double wrapping in subqueries + +* properly set 0 binding on joined subquery creation + +* properly alter renaming attributes in migration generator + +* handle original data not available in destroy_query + +* use primary key of source as join key + +* use pkey if error fields is empty + +* forgot to bind keys to a variable 🤦🏻 + +* ensure identity keys is never missing + +* properly build subqueries when required for relationship queries + +* only migrate/rollback one repo at a time + +* proper return types for updates from queries + +* allow atomics to return `nil` + +* Correct the matching used in building a distinct expression (#196) + +* only rollback to savepoint on specific errors + +* keep fields of `custom_index` in format that they were provided (#195) + +* remap selected fields, don't subquery in aggregate joins + +* include explicit schema in snapshot folder name + +* Support all_tenants? in custom index (#194) + +### Improvements: + +* update to latest ash + +* mark (i)like functions as predicates (#205) + +* detect bigserial when altering attributes + +* Include modules in installed_extensions return type (#202) + +* don't drop primary key in case of removal + +* handle if select is present on query + +* support `Ash.Changeset.OriginalDataNotAvailable` + +* support `count_nils` expression + +* `error_fields` for `custom_index` + +* support latest ash changes + ## [v1.4.0](https://github.com/ash-project/ash_postgres/compare/v1.3.68...v1.4.0) (2024-01-12) diff --git a/mix.exs b/mix.exs index 4119dd1f..194a8158 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.4.0" + @version "1.5.0" def project do [ From 96c367689d182ab4b1ddb7a95f1e0901cf6831b1 Mon Sep 17 00:00:00 2001 From: Minsub Kim Date: Mon, 19 Feb 2024 23:04:14 +0900 Subject: [PATCH 0225/1215] test: filter by has_one from_many? (#209) --- test/filter_test.exs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/filter_test.exs b/test/filter_test.exs index 649f4555..bc535bd0 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -975,4 +975,21 @@ defmodule AshPostgres.FilterTest do |> Ash.Query.filter(comments_with_high_rating.title == "foo") |> Api.read!() end + + test "filter by has_one from_many?" do + alias Ash.Changeset + alias AshPostgres.Test.ComplexCalculations.{Api, Channel, ChannelMember} + + [_cm1, cm2 | _] = + for _ <- 1..5 do + c = Changeset.for_create(Channel, :create, %{}) |> Api.create!() + Changeset.for_create(ChannelMember, :create, %{channel_id: c.id}) |> Api.create!() + end + + assert Channel + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(first_member.id != ^cm2.id) + |> Api.read!() + |> length == 4 + end end From 9edde198ef5b88cc694d949eab1719da1ced610f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 09:27:16 -0500 Subject: [PATCH 0226/1215] fix: joining to `from_many?: true` relationships not honoring limit --- lib/join.ex | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 12ab0481..6460156f 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -968,15 +968,25 @@ defmodule AshPostgres.Join do ) {:inner, false, true} -> + %Ecto.SubQuery{query: inner_query} = sub = relationship_destination + + new_inner_query = + from(row in inner_query, + where: + field(parent_as(^current_binding), ^relationship.source_attribute) == + field( + row, + ^relationship.destination_attribute + ) + ) + + relationship_destination = + %{sub | query: new_inner_query} + from([{row, current_binding}] in query, inner_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, - on: - field(row, ^relationship.source_attribute) == - field( - destination, - ^relationship.destination_attribute - ) + on: true ) {:left, true, false} -> From 49bcdd0783272d2b81c750c28a7b0b2c3bec988d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 09:27:38 -0500 Subject: [PATCH 0227/1215] chore: release version v1.5.1 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 339c0019..01156504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.1](https://github.com/ash-project/ash_postgres/compare/v1.5.0...v1.5.1) (2024-02-19) + + + + +### Bug Fixes: + +* joining to `from_many?: true` relationships not honoring limit + ## [v1.5.0](https://github.com/ash-project/ash_postgres/compare/v1.4.0...v1.5.0) (2024-02-16) diff --git a/mix.exs b/mix.exs index 194a8158..489b83b4 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.0" + @version "1.5.1" def project do [ From 0979756b0bc3cda8da0a34b6fc9570b70a8a3e90 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 11:56:48 -0500 Subject: [PATCH 0228/1215] fix: handle updating from queries w/ non-inner initial joins --- lib/data_layer.ex | 58 +++++++++++++++++++++++++++++++++++++-- lib/expr.ex | 17 +++++++----- test/bulk_update_test.exs | 18 ++++++++++++ 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3adca1db..29d85dca 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1266,9 +1266,61 @@ defmodule AshPostgres.DataLayer do try do query = - query - |> default_bindings(resource, changeset.context) - |> Ecto.Query.exclude(:select) + case Enum.at(query.joins, 0) do + nil -> + query + |> default_bindings(resource, changeset.context) + |> Ecto.Query.exclude(:select) + + %{qual: :inner} = join -> + query + |> default_bindings(resource, changeset.context) + |> Ecto.Query.exclude(:select) + + _other_type_of_join -> + root_query = + from( + row in resource, + [] + ) + |> default_bindings(resource, changeset.context) + |> Ecto.Query.exclude(:select) + + dynamic = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row, joining], + field(row, ^pkey) == field(joining, ^pkey) and ^dynamic + ) + else + Ecto.Query.dynamic([row, joining], field(row, ^pkey) == field(joining, ^pkey)) + end + end) + + faked_query = + from(row in root_query, + inner_join: limiter in ^root_query, + as: ^0, + on: ^dynamic + ) + + joins_to_add = + for {%{on: on} = join, ix} <- Enum.with_index(query.joins) do + %{join | on: Ecto.Query.Planner.rewrite_sources(on, &(&1 + 1)), ix: ix + 1} + end + + %{ + faked_query + | joins: faked_query.joins ++ joins_to_add, + aliases: Map.new(query.aliases, fn {key, val} -> {key, val + 1} end), + wheres: + faked_query.wheres ++ + Enum.map(query.wheres, fn where -> + Ecto.Query.Planner.rewrite_sources(where, &(&1 + 1)) + end) + } + end query = if options[:return_records?] do diff --git a/lib/expr.ex b/lib/expr.ex index 923be404..cdcc015c 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1364,24 +1364,28 @@ defmodule AshPostgres.Expr do {encoded, acc} = if Ash.Filter.TemplateHelpers.expr?(input) do frag_parts = - Enum.map(input, fn {key, value} -> + Enum.flat_map(input, fn {key, value} -> if Ash.Filter.TemplateHelpers.expr?(value) do [ expr: to_string(key), raw: "::text, ", - expr: value + expr: value, + raw: ", " ] else [ expr: to_string(key), raw: "::text, ", expr: value, - raw: "::jsonb" + raw: "::jsonb, " ] end end) - |> Enum.intersperse(raw: ", ") - |> List.flatten() + + frag_parts = + List.update_at(frag_parts, -1, fn {:raw, text} -> + {:raw, String.trim_trailing(text, ", ") <> "))"} + end) do_dynamic_expr( query, @@ -1393,8 +1397,7 @@ defmodule AshPostgres.Expr do expr: inspect(exception), raw: "::text, 'input', jsonb_build_object(" ] ++ - frag_parts ++ - [raw: "))"] + frag_parts }, bindings, embedded?, diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 3a229929..26096e75 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -36,6 +36,24 @@ defmodule AshPostgres.BulkUpdateTest do assert titles == ["fred_stuff", "george"] end + test "the query can join to related tables when necessary" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Logger.configure(level: :debug) + + Post + |> Ash.Query.filter(author.first_name == "fred" or title == "fred") + |> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) + + titles = + Post + |> Api.read!() + |> Enum.map(& &1.title) + |> Enum.sort() + + assert titles == ["fred_stuff", "george"] + end + test "bulk updates can be done even on stream inputs" do Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) From 7cbe482fc9221793b02b95ed238820b7bd68c690 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 12:40:26 -0500 Subject: [PATCH 0229/1215] fix: don't update_all or delete_all with `order_by` --- lib/data_layer.ex | 5 +++-- test/bulk_update_test.exs | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 29d85dca..5035e1bd 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1272,7 +1272,7 @@ defmodule AshPostgres.DataLayer do |> default_bindings(resource, changeset.context) |> Ecto.Query.exclude(:select) - %{qual: :inner} = join -> + %{qual: :inner} -> query |> default_bindings(resource, changeset.context) |> Ecto.Query.exclude(:select) @@ -1404,6 +1404,7 @@ defmodule AshPostgres.DataLayer do else query end + |> Ecto.Query.exclude(:order_by) repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) @@ -2423,7 +2424,7 @@ defmodule AshPostgres.DataLayer do result = with_savepoint(repo, query, fn -> repo.update_all( - query, + Ecto.Query.exclude(query, :order_by), [], repo_opts ) diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 26096e75..8592eb6f 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -39,8 +39,6 @@ defmodule AshPostgres.BulkUpdateTest do test "the query can join to related tables when necessary" do Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Logger.configure(level: :debug) - Post |> Ash.Query.filter(author.first_name == "fred" or title == "fred") |> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) From 99beb824a75c8e62f5123863a5b81f09923a6c79 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 12:41:43 -0500 Subject: [PATCH 0230/1215] chore: release version v1.5.2 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01156504..1d11b0f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.2](https://github.com/ash-project/ash_postgres/compare/v1.5.1...v1.5.2) (2024-02-19) + + + + +### Bug Fixes: + +* don't update_all or delete_all with `order_by` + +* handle updating from queries w/ non-inner initial joins + ## [v1.5.1](https://github.com/ash-project/ash_postgres/compare/v1.5.0...v1.5.1) (2024-02-19) diff --git a/mix.exs b/mix.exs index 489b83b4..22c41d51 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.1" + @version "1.5.2" def project do [ From 557bc6019cb357f4e4f036fb04b778d995cc0661 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 13:29:41 -0500 Subject: [PATCH 0231/1215] fix: handle non-inner joins in delete_all fix: handle non-inner joins in update --- lib/data_layer.ex | 336 +++++++++++++++++++++---------------- test/bulk_destroy_test.exs | 10 ++ 2 files changed, 206 insertions(+), 140 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5035e1bd..470f8f33 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1264,18 +1264,105 @@ defmodule AshPostgres.DataLayer do |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> ecto_changeset(changeset, :update, true, true) - try do - query = + case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do + {:error, error} -> + {:error, error} + + {:ok, query} -> + try do + query = + if options[:return_records?] do + attrs = resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) + + Ecto.Query.select(query, ^attrs) + else + query + end + + repo = dynamic_repo(resource, changeset) + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + + case query_with_atomics( + resource, + query, + ecto_changeset.filters, + changeset.atomics, + ecto_changeset.changes, + [] + ) do + :empty -> + if options[:return_records?] do + if changeset.context[:data_layer][:use_atomic_update_data?] do + {:ok, [changeset.data]} + else + {:ok, repo.all(query)} + end + else + :ok + end + + {:ok, query} -> + {_, results} = + with_savepoint(repo, query, fn -> + repo.update_all( + query, + [], + repo_opts + ) + end) + + if options[:return_records?] do + {:ok, results} + else + :ok + end + + {:error, error} -> + {:error, error} + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + end + end + end + + defp bulk_updatable_query(query, resource, atomics, context) do + Enum.reduce_while(atomics, {:ok, query}, fn {_, expr}, {:ok, query} -> + used_aggregates = + Ash.Filter.used_aggregates(expr, []) + + with {:ok, query} <- + AshPostgres.Join.join_all_relationships( + query, + %Ash.Filter{ + resource: resource, + expression: expr + }, + left_only?: true + ), + {:ok, query} <- + AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do + {:cont, {:ok, Ecto.Query.exclude(query, :order_by)}} + else + {:error, error} -> + {:halt, {:error, error}} + end + end) + |> case do + {:ok, query} -> case Enum.at(query.joins, 0) do nil -> - query - |> default_bindings(resource, changeset.context) - |> Ecto.Query.exclude(:select) + {:ok, + query + |> default_bindings(resource, context) + |> Ecto.Query.exclude(:select)} %{qual: :inner} -> - query - |> default_bindings(resource, changeset.context) - |> Ecto.Query.exclude(:select) + {:ok, + query + |> default_bindings(resource, context) + |> Ecto.Query.exclude(:select)} _other_type_of_join -> root_query = @@ -1283,7 +1370,7 @@ defmodule AshPostgres.DataLayer do row in resource, [] ) - |> default_bindings(resource, changeset.context) + |> default_bindings(resource, context) |> Ecto.Query.exclude(:select) dynamic = @@ -1310,71 +1397,21 @@ defmodule AshPostgres.DataLayer do %{join | on: Ecto.Query.Planner.rewrite_sources(on, &(&1 + 1)), ix: ix + 1} end - %{ - faked_query - | joins: faked_query.joins ++ joins_to_add, - aliases: Map.new(query.aliases, fn {key, val} -> {key, val + 1} end), - wheres: - faked_query.wheres ++ - Enum.map(query.wheres, fn where -> - Ecto.Query.Planner.rewrite_sources(where, &(&1 + 1)) - end) - } - end - - query = - if options[:return_records?] do - attrs = resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) - - Ecto.Query.select(query, ^attrs) - else - query + {:ok, + %{ + faked_query + | joins: faked_query.joins ++ joins_to_add, + aliases: Map.new(query.aliases, fn {key, val} -> {key, val + 1} end), + wheres: + faked_query.wheres ++ + Enum.map(query.wheres, fn where -> + Ecto.Query.Planner.rewrite_sources(where, &(&1 + 1)) + end) + }} end - repo = dynamic_repo(resource, changeset) - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - - case query_with_atomics( - resource, - query, - ecto_changeset.filters, - changeset.atomics, - ecto_changeset.changes, - [] - ) do - :empty -> - if options[:return_records?] do - if changeset.context[:data_layer][:use_atomic_update_data?] do - {:ok, [changeset.data]} - else - {:ok, repo.all(query)} - end - else - :ok - end - - {:ok, query} -> - {_, results} = - with_savepoint(repo, query, fn -> - repo.update_all( - query, - [], - repo_opts - ) - end) - - if options[:return_records?] do - {:ok, results} - else - :ok - end - - {:error, error} -> - {:error, error} - end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + {:error, error} -> + {:error, error} end end @@ -1413,6 +1450,36 @@ defmodule AshPostgres.DataLayer do repo = dynamic_repo(resource, changeset) + query = + if Enum.any?(query.joins, &(&1.qual != :inner)) do + root_query = + from( + row in resource, + [] + ) + |> default_bindings(resource, changeset.context) + |> Ecto.Query.exclude(:select) + + on = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row, distinct], + ^dynamic and field(row, ^key) == field(distinct, ^key) + ) + else + Ecto.Query.dynamic([row, distinct], field(row, ^key) == field(distinct, ^key)) + end + end) + + from(row in root_query, + join: subquery(query), + on: ^on + ) + else + query + end + {_, results} = with_savepoint(repo, query, fn -> repo.delete_all( @@ -2392,69 +2459,72 @@ defmodule AshPostgres.DataLayer do |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> ecto_changeset(changeset, :update) - try do - query = from(row in resource, as: ^0) + query = from(row in resource, as: ^0) |> default_bindings(resource, changeset.context) - select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) + select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) - query = - query - |> default_bindings(resource, changeset.context) - |> Ecto.Query.select(^select) - - case query_with_atomics( - resource, - query, - ecto_changeset.filters, - changeset.atomics, - ecto_changeset.changes, - [] - ) do - :empty -> - {:ok, changeset.data} - - {:ok, query} -> - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do + {:error, error} -> + {:error, error} - repo_opts = - Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) + {:ok, query} -> + query = Ecto.Query.select(query, ^select) - repo = dynamic_repo(resource, changeset) + try do + case query_with_atomics( + resource, + query, + ecto_changeset.filters, + changeset.atomics, + ecto_changeset.changes, + [] + ) do + :empty -> + {:ok, changeset.data} - result = - with_savepoint(repo, query, fn -> - repo.update_all( - Ecto.Query.exclude(query, :order_by), - [], - repo_opts - ) - end) + {:ok, query} -> + repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - case result do - {0, []} -> - {:error, - Ash.Error.Changes.StaleRecord.exception( - resource: resource, - filters: ecto_changeset.filters - )} + repo_opts = + Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - {1, [result]} -> - record = - changeset.data - |> Map.merge(changeset.attributes) - |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) + repo = dynamic_repo(resource, changeset) - maybe_update_tenant(resource, changeset, record) + result = + with_savepoint(repo, query, fn -> + repo.update_all( + query, + [], + repo_opts + ) + end) - {:ok, record} - end + case result do + {0, []} -> + {:error, + Ash.Error.Changes.StaleRecord.exception( + resource: resource, + filters: ecto_changeset.filters + )} - {:error, error} -> - {:error, error} - end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + {1, [result]} -> + record = + changeset.data + |> Map.merge(changeset.attributes) + |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) + + maybe_update_tenant(resource, changeset, record) + + {:ok, record} + end + + {:error, error} -> + {:error, error} + end + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + end end end @@ -2475,25 +2545,11 @@ defmodule AshPostgres.DataLayer do atomics_result = Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> - used_aggregates = - Ash.Filter.used_aggregates(expr, []) - attribute = Ash.Resource.Info.attribute(resource, field) type = AshPostgres.Types.parameterized_type(attribute.type, attribute.constraints) - with {:ok, query} <- - AshPostgres.Join.join_all_relationships( - query, - %Ash.Filter{ - resource: resource, - expression: expr - }, - left_only?: true - ), - {:ok, query} <- - AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0), - {dynamic, acc} <- + with {dynamic, acc} <- AshPostgres.Expr.dynamic_expr( query, expr, diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index 5327a379..e0da62e2 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -28,6 +28,16 @@ defmodule AshPostgres.BulkDestroyTest do assert [%{title: "george"}] = Api.read!(Post) end + test "the query can join to related tables when necessary" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.Query.filter(author.first_name == "fred" or title == "fred") + |> Api.bulk_destroy!(:update, %{}) + + assert [%{title: "george"}] = Api.read!(Post) + end + test "bulk destroys can be done even on stream inputs" do Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) From f9a59cc5f638397c58f7e51c872b5843353b3069 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 13:35:36 -0500 Subject: [PATCH 0232/1215] chore: update style for credo --- lib/data_layer.ex | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 470f8f33..ca0904f1 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2549,16 +2549,16 @@ defmodule AshPostgres.DataLayer do type = AshPostgres.Types.parameterized_type(attribute.type, attribute.constraints) - with {dynamic, acc} <- - AshPostgres.Expr.dynamic_expr( - query, - expr, - query.__ash_bindings__, - false, - type - ) do - {:cont, {:ok, merge_expr_accumulator(query, acc), Keyword.put(set, field, dynamic)}} - else + case AshPostgres.Expr.dynamic_expr( + query, + expr, + query.__ash_bindings__, + false, + type + ) do + {dynamic, acc} -> + {:cont, {:ok, merge_expr_accumulator(query, acc), Keyword.put(set, field, dynamic)}} + other -> {:halt, other} end From 40522aadd865a590cd89212f5a885180d4dfc0b7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 13:49:11 -0500 Subject: [PATCH 0233/1215] chore: properly set __ash_bindings__ on bulk updatable query --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ca0904f1..5992b43c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1370,7 +1370,7 @@ defmodule AshPostgres.DataLayer do row in resource, [] ) - |> default_bindings(resource, context) + |> Map.put(:__ash_bindings__, query.__ash_bindings__) |> Ecto.Query.exclude(:select) dynamic = From be5b6293bf4ab3a07673f9fbc4744dcdf94547aa Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 Feb 2024 16:57:34 -0500 Subject: [PATCH 0234/1215] chore: release version v1.5.3 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d11b0f6..cd28208b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.3](https://github.com/ash-project/ash_postgres/compare/v1.5.2...v1.5.3) (2024-02-19) + + + + +### Bug Fixes: + +* handle non-inner joins in delete_all + +* handle non-inner joins in update + ## [v1.5.2](https://github.com/ash-project/ash_postgres/compare/v1.5.1...v1.5.2) (2024-02-19) diff --git a/mix.exs b/mix.exs index 22c41d51..44396c0e 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.2" + @version "1.5.3" def project do [ From d2500b25f379a45b8d33804f7d5e7637b4388dbb Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Tue, 20 Feb 2024 18:15:06 +0200 Subject: [PATCH 0235/1215] fix: properly transfer table names to non-inner wrapper queries (#210) --- lib/data_layer.ex | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5992b43c..bac1c5fa 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1366,10 +1366,7 @@ defmodule AshPostgres.DataLayer do _other_type_of_join -> root_query = - from( - row in resource, - [] - ) + from(row in query.from.source, []) |> Map.put(:__ash_bindings__, query.__ash_bindings__) |> Ecto.Query.exclude(:select) @@ -1453,10 +1450,7 @@ defmodule AshPostgres.DataLayer do query = if Enum.any?(query.joins, &(&1.qual != :inner)) do root_query = - from( - row in resource, - [] - ) + from(row in query.from.source, []) |> default_bindings(resource, changeset.context) |> Ecto.Query.exclude(:select) @@ -1511,8 +1505,8 @@ defmodule AshPostgres.DataLayer do end changesets = Enum.to_list(stream) - repo = dynamic_repo(resource, Enum.at(changesets, 0)) + source = resolve_source(resource, Enum.at(changesets, 0)) try do opts = @@ -1521,7 +1515,7 @@ defmodule AshPostgres.DataLayer do # this means that all changesets have the same atomics %{atomics: atomics, filters: filters} = Enum.at(changesets, 0) - query = from(row in resource, as: ^0) + query = from(row in source, as: ^0) query = query @@ -1564,13 +1558,6 @@ defmodule AshPostgres.DataLayer do ecto_changesets = Enum.map(changesets, & &1.attributes) - source = - if table = Enum.at(changesets, 0).context[:data_layer][:table] do - {table, resource} - else - resource - end - opts = if schema = Enum.at(changesets, 0).context[:data_layer][:schema] do Keyword.put(opts, :prefix, schema) @@ -2459,7 +2446,8 @@ defmodule AshPostgres.DataLayer do |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> ecto_changeset(changeset, :update) - query = from(row in resource, as: ^0) |> default_bindings(resource, changeset.context) + source = resolve_source(resource, changeset) + query = from(row in source, as: ^0) |> default_bindings(resource, changeset.context) select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) @@ -3165,4 +3153,12 @@ defmodule AshPostgres.DataLayer do Ecto.Changeset -> :mutate end end + + defp resolve_source(resource, changeset) do + if table = changeset.context[:data_layer][:table] do + {table, resource} + else + resource + end + end end From 46928521c011e84b6b243254d08a5f400e8e6ceb Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Tue, 20 Feb 2024 18:15:56 +0200 Subject: [PATCH 0236/1215] fix: use proper tables in joins originating from polymorphic resource (#211) --- lib/join.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/join.ex b/lib/join.ex index 6460156f..b534e1e6 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -232,6 +232,7 @@ defmodule AshPostgres.Join do |> Ash.Query.new(nil, base_filter?: false) |> Ash.Query.set_context(%{data_layer: %{start_bindings_at: start_binding}}) |> Ash.Query.set_context(context) + |> Ash.Query.set_context(%{data_layer: %{table: nil}}) |> Ash.Query.set_context(relationship.context) |> Ash.Query.for_read(read_action, %{}, actor: context[:private][:actor], From 38d1beb0d810d71e0244794b4fdd90035e354e07 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 Feb 2024 18:38:35 -0500 Subject: [PATCH 0237/1215] fix: properly handle to_many joins in aggregates fix: honor aggregate query filters --- lib/aggregate.ex | 64 ++++++++++++++++++++++++++------- lib/data_layer.ex | 80 +++++++++++++++++++++++++++++++++-------- lib/join.ex | 16 +++++++-- test/aggregate_test.exs | 13 +++++++ 4 files changed, 142 insertions(+), 31 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 7f433d9e..f9f9c352 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -754,10 +754,11 @@ defmodule AshPostgres.Aggregate do end end - defp can_group?(_, %{kind: :exists}), do: false - defp can_group?(_, %{kind: :list}), do: false + @doc false + def can_group?(_, %{kind: :exists}), do: false + def can_group?(_, %{kind: :list}), do: false - defp can_group?(resource, aggregate) do + def can_group?(resource, aggregate) do can_group_kind?(aggregate, resource) && !has_exists?(aggregate) && !references_relationships?(aggregate) end @@ -768,13 +769,30 @@ defmodule AshPostgres.Aggregate do # 2. potentially group them all up that join to relationships and just join to all the relationships # but this method is predictable and easy so we're starting by just not grouping them defp references_relationships?(aggregate) do - !!Ash.Filter.find(aggregate.query && aggregate.query.filter, fn - %Ash.Query.Ref{relationship_path: relationship_path} when relationship_path != [] -> + if aggregate.query do + aggregate.query.filter + |> Ash.Filter.relationship_paths() + |> Enum.any?(&to_many_path?(aggregate.query.resource, &1)) + else + false + end + end + + defp to_many_path?(_resource, []), do: false + + defp to_many_path?(resource, [rel | rest]) do + case Ash.Resource.Info.relationship(resource, rel) do + %{cardinality: :many} -> true - _ -> - false - end) + nil -> + raise """ + No such relationship #{inspect(rel)} for resource #{inspect(resource)} + """ + + rel -> + to_many_path?(rel.destination, [rest]) + end end defp can_group_kind?(aggregate, resource) do @@ -982,7 +1000,8 @@ defmodule AshPostgres.Aggregate do ), query} end - {query, filtered} = filter_field(sorted, query, aggregate, relationship_path, is_single?) + {query, filtered} = + filter_field(sorted, query, aggregate, relationship_path, is_single?) value = Ecto.Query.dynamic(fragment("(?)[1]", ^filtered)) @@ -1004,8 +1023,10 @@ defmodule AshPostgres.Aggregate do with_default end + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + select_or_merge( - AshPostgres.DataLayer.merge_expr_accumulator(query, acc), + query, aggregate.name, casted ) @@ -1100,7 +1121,8 @@ defmodule AshPostgres.Aggregate do end end - {query, filtered} = filter_field(sorted, query, aggregate, relationship_path, is_single?) + {query, filtered} = + filter_field(sorted, query, aggregate, relationship_path, is_single?) with_default = if aggregate.default_value do @@ -1120,8 +1142,10 @@ defmodule AshPostgres.Aggregate do with_default end + query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + select_or_merge( - AshPostgres.DataLayer.merge_expr_accumulator(query, acc), + query, aggregate.name, cast ) @@ -1231,8 +1255,22 @@ defmodule AshPostgres.Aggregate do used_aggregates = Ash.Filter.used_aggregates(filter, []) + # here we bypass an inner join. + # Really, we should check if all aggs in a group + # could do the same inner join, then do an inner join {:ok, query} = - AshPostgres.Join.join_all_relationships(query, filter) + AshPostgres.Join.join_all_relationships( + query, + filter, + [], + nil, + [], + nil, + true, + nil, + nil, + true + ) {:ok, query} = AshPostgres.Aggregate.add_aggregates( diff --git a/lib/data_layer.ex b/lib/data_layer.ex index bac1c5fa..2bb10bcf 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -754,7 +754,14 @@ defmodule AshPostgres.DataLayer do @impl true def run_aggregate_query(query, aggregates, resource) do - {exists, aggregates} = Enum.split_with(aggregates, &(&1.kind == :exists)) + {can_group, cant_group} = + aggregates + |> Enum.split_with(&AshPostgres.Aggregate.can_group?(resource, &1)) + |> case do + {[one], cant_group} -> {[], [one | cant_group]} + {can_group, cant_group} -> {can_group, cant_group} + end + query = default_bindings(query, resource) query = @@ -778,7 +785,7 @@ defmodule AshPostgres.DataLayer do query = Enum.reduce( - aggregates, + can_group, query, fn agg, query -> first_relationship = @@ -789,14 +796,14 @@ defmodule AshPostgres.DataLayer do agg.relationship_path |> Enum.drop(1), agg, resource, - true, + false, first_relationship ) end ) result = - case aggregates do + case can_group do [] -> %{} @@ -804,14 +811,11 @@ defmodule AshPostgres.DataLayer do dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) end - {:ok, add_exists_aggs(result, resource, query_before_select, exists)} + {:ok, add_single_aggs(result, resource, query_before_select, cant_group)} end - defp add_exists_aggs(result, resource, query, exists) do - repo = dynamic_repo(resource, query) - repo_opts = repo_opts(nil, nil, resource) - - Enum.reduce(exists, result, fn agg, result -> + defp add_single_aggs(result, resource, query, cant_group) do + Enum.reduce(cant_group, result, fn agg, result -> {:ok, filtered} = case agg do %{query: %{filter: filter}} when not is_nil(filter) -> @@ -821,10 +825,50 @@ defmodule AshPostgres.DataLayer do {:ok, query} end + filtered = + if filtered.distinct do + in_query = filtered |> Ecto.Query.exclude(:distinct) |> Ecto.Query.exclude(:select) + + dynamic = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row], + ^dynamic and field(parent_as(^0), ^key) == field(row, ^key) + ) + else + Ecto.Query.dynamic( + [row], + field(parent_as(^0), ^key) == field(row, ^key) + ) + end + end) + + in_query = + from(row in in_query, where: ^dynamic) + + from(row in query.from.source, as: ^0, where: exists(in_query)) + else + filtered + end + + first_relationship = + Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + + query = + AshPostgres.Aggregate.add_subquery_aggregate_select( + filtered, + agg.relationship_path |> Enum.drop(1), + %{agg | query: %{agg.query | filter: nil}}, + resource, + true, + first_relationship + ) + Map.put( result || %{}, agg.name, - repo.exists?(filtered, repo_opts) + dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) ) end) end @@ -842,7 +886,13 @@ defmodule AshPostgres.DataLayer do destination_resource, path ) do - {exists, aggregates} = Enum.split_with(aggregates, &(&1.kind == :exists)) + {can_group, cant_group} = + aggregates + |> Enum.split_with(&AshPostgres.Aggregate.can_group?(destination_resource, &1)) + |> case do + {[one], cant_group} -> {[], [one | cant_group]} + {can_group, cant_group} -> {can_group, cant_group} + end case lateral_join_query( query, @@ -861,7 +911,7 @@ defmodule AshPostgres.DataLayer do query = Enum.reduce( - aggregates, + can_group, subquery, fn agg, subquery -> has_exists? = @@ -888,7 +938,7 @@ defmodule AshPostgres.DataLayer do ) result = - case aggregates do + case can_group do [] -> %{} @@ -899,7 +949,7 @@ defmodule AshPostgres.DataLayer do ) end - {:ok, add_exists_aggs(result, source_resource, subquery, exists)} + {:ok, add_single_aggs(result, source_resource, subquery, cant_group)} {:error, error} -> {:error, error} diff --git a/lib/join.ex b/lib/join.ex index b534e1e6..39481cc5 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -32,7 +32,8 @@ defmodule AshPostgres.Join do source \\ nil, sort? \\ true, join_filters \\ nil, - parent_bindings \\ nil + parent_bindings \\ nil, + no_inner_join? \\ false ) # simple optimization for common cases @@ -45,7 +46,8 @@ defmodule AshPostgres.Join do _source, _sort?, _join_filters, - _parent_bindings + _parent_bindings, + _no_inner_join? ) when is_nil(relationship_paths) and filter in [nil, true, false] do {:ok, query} @@ -60,7 +62,8 @@ defmodule AshPostgres.Join do source, sort?, join_filters, - parent_query + parent_query, + no_inner_join? ) do relationship_paths = cond do @@ -90,6 +93,13 @@ defmodule AshPostgres.Join do {:cont, {:ok, query}} {join_type, [relationship | rest_rels]}, {:ok, query} -> + join_type = + if no_inner_join? do + :left + else + join_type + end + source = source || relationship.source current_path = path ++ [relationship] diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 9bf73567..7494511d 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -3,6 +3,7 @@ defmodule AshPostgres.AggregateTest do alias AshPostgres.Test.{Api, Author, Comment, Organization, Post, Rating, User} require Ash.Query + require Ash.Expr test "relates to actor via has_many and with an aggregate" do org = @@ -1163,6 +1164,18 @@ defmodule AshPostgres.AggregateTest do |> Api.count!() end + test "a count can filter independently of the query" do + Post + |> Api.aggregate([ + Ash.Query.Aggregate.new!(Post, :count, :count, + query: [filter: Ash.Expr.expr(comments.likes > 10)] + ), + Ash.Query.Aggregate.new!(Post, :count2, :count, + query: [filter: Ash.Expr.expr(comments.likes < 10)] + ) + ]) + end + test "a count with a filter that references a relationship combined with another" do post = Post From b5b1077032bd178572b7387006cef18da2afb8d4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 Feb 2024 22:13:23 -0500 Subject: [PATCH 0238/1215] chore: fix recursion error in to_many_path? --- lib/aggregate.ex | 2 +- test/aggregate_test.exs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index f9f9c352..6373044e 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -791,7 +791,7 @@ defmodule AshPostgres.Aggregate do """ rel -> - to_many_path?(rel.destination, [rest]) + to_many_path?(rel.destination, rest) end end diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 7494511d..326945bc 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1167,12 +1167,8 @@ defmodule AshPostgres.AggregateTest do test "a count can filter independently of the query" do Post |> Api.aggregate([ - Ash.Query.Aggregate.new!(Post, :count, :count, - query: [filter: Ash.Expr.expr(comments.likes > 10)] - ), - Ash.Query.Aggregate.new!(Post, :count2, :count, - query: [filter: Ash.Expr.expr(comments.likes < 10)] - ) + {:count, :count, query: [filter: Ash.Expr.expr(comments.likes > 10)]}, + {:count2, :count, query: [filter: Ash.Expr.expr(comments.likes < 10)]} ]) end From a4dd0855bafacad8204afcc686071c5539249de3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 Feb 2024 22:27:25 -0500 Subject: [PATCH 0239/1215] chore: credo --- .credo.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.credo.exs b/.credo.exs index fd0b1164..4d3609ad 100644 --- a/.credo.exs +++ b/.credo.exs @@ -117,7 +117,7 @@ # {Credo.Check.Refactor.CondStatements, []}, {Credo.Check.Refactor.CyclomaticComplexity, false}, - {Credo.Check.Refactor.FunctionArity, [max_arity: 9]}, + {Credo.Check.Refactor.FunctionArity, [max_arity: 10]}, {Credo.Check.Refactor.LongQuoteBlocks, []}, {Credo.Check.Refactor.MapInto, []}, {Credo.Check.Refactor.MatchInCondition, []}, From 6e81afb24b2c3014b658e9104465d5bed793ca0f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 07:54:58 -0500 Subject: [PATCH 0240/1215] fix: don't sort a query that will be used with `delete_all` fix: ensure that `exists?` aggregates use `repo.exists?` --- lib/data_layer.ex | 117 +++++++++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2bb10bcf..701d8802 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -815,61 +815,80 @@ defmodule AshPostgres.DataLayer do end defp add_single_aggs(result, resource, query, cant_group) do - Enum.reduce(cant_group, result, fn agg, result -> - {:ok, filtered} = - case agg do - %{query: %{filter: filter}} when not is_nil(filter) -> - filter(query, filter, resource) - - _ -> - {:ok, query} - end + Enum.reduce(cant_group, result, fn + %{kind: :exists} = agg, result -> + {:ok, filtered} = + case agg do + %{query: %{filter: filter}} when not is_nil(filter) -> + filter(query, filter, resource) - filtered = - if filtered.distinct do - in_query = filtered |> Ecto.Query.exclude(:distinct) |> Ecto.Query.exclude(:select) + _ -> + {:ok, query} + end - dynamic = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row], - ^dynamic and field(parent_as(^0), ^key) == field(row, ^key) - ) - else - Ecto.Query.dynamic( - [row], - field(parent_as(^0), ^key) == field(row, ^key) - ) - end - end) + filtered = Ecto.Query.exclude(filtered, :distinct) - in_query = - from(row in in_query, where: ^dynamic) + Map.put( + result || %{}, + agg.name, + dynamic_repo(resource, filtered).exists?(filtered, repo_opts(nil, nil, resource)) + ) - from(row in query.from.source, as: ^0, where: exists(in_query)) - else - filtered - end + agg, result -> + {:ok, filtered} = + case agg do + %{query: %{filter: filter}} when not is_nil(filter) -> + filter(query, filter, resource) - first_relationship = - Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + _ -> + {:ok, query} + end - query = - AshPostgres.Aggregate.add_subquery_aggregate_select( - filtered, - agg.relationship_path |> Enum.drop(1), - %{agg | query: %{agg.query | filter: nil}}, - resource, - true, - first_relationship - ) + filtered = + if filtered.distinct do + in_query = filtered |> Ecto.Query.exclude(:distinct) |> Ecto.Query.exclude(:select) - Map.put( - result || %{}, - agg.name, - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) - ) + dynamic = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row], + ^dynamic and field(parent_as(^0), ^key) == field(row, ^key) + ) + else + Ecto.Query.dynamic( + [row], + field(parent_as(^0), ^key) == field(row, ^key) + ) + end + end) + + in_query = + from(row in in_query, where: ^dynamic) + + from(row in query.from.source, as: ^0, where: exists(in_query)) + else + filtered + end + + first_relationship = + Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + + query = + AshPostgres.Aggregate.add_subquery_aggregate_select( + filtered, + agg.relationship_path |> Enum.drop(1), + %{agg | query: %{agg.query | filter: nil}}, + resource, + true, + first_relationship + ) + + Map.put( + result || %{}, + agg.name, + dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + ) end) end @@ -1521,7 +1540,7 @@ defmodule AshPostgres.DataLayer do on: ^on ) else - query + Ecto.Query.exclude(query, :select) end {_, results} = From 501b8f0d9a4860365a2db5d593b37ac16317668c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 07:55:33 -0500 Subject: [PATCH 0241/1215] chore: release version v1.5.4 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd28208b..0dcd645e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.4](https://github.com/ash-project/ash_postgres/compare/v1.5.3...v1.5.4) (2024-02-21) + + + + +### Bug Fixes: + +* don't sort a query that will be used with `delete_all` + +* ensure that `exists?` aggregates use `repo.exists?` + +* properly handle to_many joins in aggregates + +* honor aggregate query filters + +* use proper tables in joins originating from polymorphic resource (#211) + +* properly transfer table names to non-inner wrapper queries (#210) + ## [v1.5.3](https://github.com/ash-project/ash_postgres/compare/v1.5.2...v1.5.3) (2024-02-19) diff --git a/mix.exs b/mix.exs index 44396c0e..15e53c0e 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.3" + @version "1.5.4" def project do [ From 2b91ebe488d22c67b433d4e4d416c67890df39cb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 09:56:33 -0500 Subject: [PATCH 0242/1215] fix: ensure proper return value for single aggregate runs --- lib/data_layer.ex | 3 +-- test/aggregate_test.exs | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 701d8802..930251dd 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -884,9 +884,8 @@ defmodule AshPostgres.DataLayer do first_relationship ) - Map.put( + Map.merge( result || %{}, - agg.name, dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) ) end) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 326945bc..0bc4daeb 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1165,11 +1165,12 @@ defmodule AshPostgres.AggregateTest do end test "a count can filter independently of the query" do - Post - |> Api.aggregate([ - {:count, :count, query: [filter: Ash.Expr.expr(comments.likes > 10)]}, - {:count2, :count, query: [filter: Ash.Expr.expr(comments.likes < 10)]} - ]) + assert {:ok, %{count: 0, count2: 0}} = + Post + |> Api.aggregate([ + {:count, :count, query: [filter: Ash.Expr.expr(comments.likes > 10)]}, + {:count2, :count, query: [filter: Ash.Expr.expr(comments.likes < 10)]} + ]) end test "a count with a filter that references a relationship combined with another" do From a1c9c4e022e14617807d47856224062b1598a68d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 09:56:47 -0500 Subject: [PATCH 0243/1215] chore: release version v1.5.5 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dcd645e..dc43fad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.5](https://github.com/ash-project/ash_postgres/compare/v1.5.4...v1.5.5) (2024-02-21) + + + + +### Bug Fixes: + +* ensure proper return value for single aggregate runs + ## [v1.5.4](https://github.com/ash-project/ash_postgres/compare/v1.5.3...v1.5.4) (2024-02-21) diff --git a/mix.exs b/mix.exs index 15e53c0e..00b1d202 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.4" + @version "1.5.5" def project do [ From d0041ce88d9528ba34575ea24774d27f6b9080f4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 14:03:28 -0500 Subject: [PATCH 0244/1215] improvement: optimize aggregate query filtering --- lib/aggregate.ex | 61 ++++++++++++++- lib/data_layer.ex | 163 ++++++++++++++++++++++++---------------- test/aggregate_test.exs | 19 +++++ 3 files changed, 178 insertions(+), 65 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 6373044e..5093fdab 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -257,6 +257,63 @@ defmodule AshPostgres.Aggregate do ) end + @doc false + def extract_shared_filters(aggregates) do + aggregates + |> Enum.reduce_while({nil, []}, fn + %{query: %{filter: filter}} = agg, {global_filters, aggs} when not is_nil(filter) -> + and_statements = + AshPostgres.DataLayer.split_and_statements(filter) + + global_filters = + if global_filters do + Enum.filter(global_filters, &(&1 in and_statements)) + else + and_statements + end + + {:cont, {global_filters, [{agg, and_statements} | aggs]}} + + _, _ -> + {:halt, {:error, aggregates}} + end) + |> case do + {:error, aggregates} -> + {:error, aggregates} + + {[], _} -> + {:error, aggregates} + + {nil, _} -> + {:error, aggregates} + + {global_filters, aggregates} -> + global_filter = and_filters(Enum.uniq(global_filters)) + + aggregates = + Enum.map(aggregates, fn {agg, and_statements} -> + applicable_and_statements = + and_statements + |> Enum.reject(&(&1 in global_filters)) + |> and_filters() + + %{agg | query: %{agg.query | filter: applicable_and_statements}} + end) + + {{:ok, global_filter}, aggregates} + end + end + + defp and_filters(filters) do + Enum.reduce(filters, nil, fn expr, acc -> + if is_nil(acc) do + expr + else + Ash.Query.BooleanExpression.new(:and, expr, acc) + end + end) + end + defp apply_first_relationship_join_filters( agg_root_query, query, @@ -760,7 +817,7 @@ defmodule AshPostgres.Aggregate do def can_group?(resource, aggregate) do can_group_kind?(aggregate, resource) && !has_exists?(aggregate) && - !references_relationships?(aggregate) + !references_to_many_relationships?(aggregate) end # We can potentially optimize this. We don't have to prevent aggregates that reference @@ -768,7 +825,7 @@ defmodule AshPostgres.Aggregate do # 1. group up the ones that do join relationships by the relationships they join # 2. potentially group them all up that join to relationships and just join to all the relationships # but this method is predictable and easy so we're starting by just not grouping them - defp references_relationships?(aggregate) do + defp references_to_many_relationships?(aggregate) do if aggregate.query do aggregate.query.filter |> Ash.Filter.relationship_paths() diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 930251dd..758561f5 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -783,35 +783,53 @@ defmodule AshPostgres.DataLayer do query_before_select = query + {global_filter, can_group} = + AshPostgres.Aggregate.extract_shared_filters(can_group) + query = - Enum.reduce( - can_group, - query, - fn agg, query -> - first_relationship = - Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + case global_filter do + {:ok, global_filter} -> + filter(query, global_filter, resource) - AshPostgres.Aggregate.add_subquery_aggregate_select( + :error -> + {:ok, query} + end + + case query do + {:error, error} -> + {:error, error} + + {:ok, query} -> + query = + Enum.reduce( + can_group, query, - agg.relationship_path |> Enum.drop(1), - agg, - resource, - false, - first_relationship + fn agg, query -> + first_relationship = + Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + + AshPostgres.Aggregate.add_subquery_aggregate_select( + query, + agg.relationship_path |> Enum.drop(1), + agg, + resource, + false, + first_relationship + ) + end ) - end - ) - result = - case can_group do - [] -> - %{} + result = + case can_group do + [] -> + %{} - _ -> - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) - end + _ -> + dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + end - {:ok, add_single_aggs(result, resource, query_before_select, cant_group)} + {:ok, add_single_aggs(result, resource, query_before_select, cant_group)} + end end defp add_single_aggs(result, resource, query, cant_group) do @@ -927,47 +945,65 @@ defmodule AshPostgres.DataLayer do subquery = from(row in subquery(lateral_join_query), as: ^0, select: %{}) subquery = default_bindings(subquery, source_resource) - query = - Enum.reduce( - can_group, - subquery, - fn agg, subquery -> - has_exists? = - Ash.Filter.find(agg.query && agg.query.filter, fn - %Ash.Query.Exists{} -> true - _ -> false - end) + {global_filter, can_group} = + AshPostgres.Aggregate.extract_shared_filters(can_group) - first_relationship = - Ash.Resource.Info.relationship( - source_resource, - agg.relationship_path |> Enum.at(0) - ) + subquery = + case global_filter do + {:ok, global_filter} -> + filter(subquery, global_filter, destination_resource) - AshPostgres.Aggregate.add_subquery_aggregate_select( + :error -> + {:ok, subquery} + end + + case subquery do + {:error, error} -> + {:error, error} + + {:ok, subquery} -> + query = + Enum.reduce( + can_group, subquery, - agg.relationship_path |> Enum.drop(1), - agg, - destination_resource, - has_exists?, - first_relationship + fn agg, subquery -> + has_exists? = + Ash.Filter.find(agg.query && agg.query.filter, fn + %Ash.Query.Exists{} -> true + _ -> false + end) + + first_relationship = + Ash.Resource.Info.relationship( + source_resource, + agg.relationship_path |> Enum.at(0) + ) + + AshPostgres.Aggregate.add_subquery_aggregate_select( + subquery, + agg.relationship_path |> Enum.drop(1), + agg, + destination_resource, + has_exists?, + first_relationship + ) + end ) - end - ) - result = - case can_group do - [] -> - %{} + result = + case can_group do + [] -> + %{} - _ -> - dynamic_repo(source_resource, query).one( - query, - repo_opts(nil, nil, source_resource) - ) - end + _ -> + dynamic_repo(source_resource, query).one( + query, + repo_opts(nil, nil, source_resource) + ) + end - {:ok, add_single_aggs(result, source_resource, subquery, cant_group)} + {:ok, add_single_aggs(result, source_resource, subquery, cant_group)} + end {:error, error} -> {:error, error} @@ -3103,21 +3139,22 @@ defmodule AshPostgres.DataLayer do end) end - defp split_and_statements(%Filter{expression: expression}) do + @doc false + def split_and_statements(%Filter{expression: expression}) do split_and_statements(expression) end - defp split_and_statements(%BooleanExpression{op: :and, left: left, right: right}) do + def split_and_statements(%BooleanExpression{op: :and, left: left, right: right}) do split_and_statements(left) ++ split_and_statements(right) end - defp split_and_statements(%Not{expression: %Not{expression: expression}}) do + def split_and_statements(%Not{expression: %Not{expression: expression}}) do split_and_statements(expression) end - defp split_and_statements(%Not{ - expression: %BooleanExpression{op: :or, left: left, right: right} - }) do + def split_and_statements(%Not{ + expression: %BooleanExpression{op: :or, left: left, right: right} + }) do split_and_statements(%BooleanExpression{ op: :and, left: %Not{expression: left}, @@ -3125,7 +3162,7 @@ defmodule AshPostgres.DataLayer do }) end - defp split_and_statements(other), do: [other] + def split_and_statements(other), do: [other] @doc false def add_binding(query, data, additional_bindings \\ 0) do diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 0bc4daeb..4e7fc2e2 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1173,6 +1173,25 @@ defmodule AshPostgres.AggregateTest do ]) end + test "multiple aggregates will be grouped up if possible" do + assert {:ok, %{count: 0, count2: 0}} = + Post + |> Api.aggregate([ + {:count, :count, + query: [ + filter: + Ash.Expr.expr(author.first_name == "fred" and author.last_name == "weasley") + ]}, + {:count2, :count, + query: [ + filter: + Ash.Expr.expr( + author.first_name == "george" and author.last_name == "weasley" + ) + ]} + ]) + end + test "a count with a filter that references a relationship combined with another" do post = Post From c185ba80e84e224526d6dded3059a3281d52daf4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 16:37:28 -0500 Subject: [PATCH 0245/1215] fix: ensure select is properly set on delete_all --- lib/data_layer.ex | 4 +++- test/bulk_destroy_test.exs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 758561f5..9a4a6a29 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1557,6 +1557,7 @@ defmodule AshPostgres.DataLayer do from(row in query.from.source, []) |> default_bindings(resource, changeset.context) |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) on = Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> @@ -1571,11 +1572,12 @@ defmodule AshPostgres.DataLayer do end) from(row in root_query, + select: row, join: subquery(query), on: ^on ) else - Ecto.Query.exclude(query, :select) + Ecto.Query.exclude(query, :order_by) end {_, results} = diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index e0da62e2..6fe4c8a7 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -33,7 +33,7 @@ defmodule AshPostgres.BulkDestroyTest do Post |> Ash.Query.filter(author.first_name == "fred" or title == "fred") - |> Api.bulk_destroy!(:update, %{}) + |> Api.bulk_destroy!(:update, %{}, return_records?: true) assert [%{title: "george"}] = Api.read!(Post) end From efded4ee0ab72639efe67c50c44f3b5da8487c81 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 Feb 2024 16:38:43 -0500 Subject: [PATCH 0246/1215] chore: release version v1.5.6 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc43fad3..9c5b1155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.6](https://github.com/ash-project/ash_postgres/compare/v1.5.5...v1.5.6) (2024-02-21) + + + + +### Bug Fixes: + +* ensure select is properly set on delete_all + +### Improvements: + +* optimize aggregate query filtering + ## [v1.5.5](https://github.com/ash-project/ash_postgres/compare/v1.5.4...v1.5.5) (2024-02-21) diff --git a/mix.exs b/mix.exs index 00b1d202..b98a7780 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.5" + @version "1.5.6" def project do [ From 5052463767970ee24d234d908ff47482e46137f9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 22 Feb 2024 07:31:06 -0500 Subject: [PATCH 0247/1215] fix: properly apply lateral join conditions to left lateral joins --- lib/join.ex | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 39481cc5..ed22a479 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -1027,15 +1027,25 @@ defmodule AshPostgres.Join do ) {:left, false, true} -> + %Ecto.SubQuery{query: inner_query} = sub = relationship_destination + + new_inner_query = + from(row in inner_query, + where: + field(parent_as(^current_binding), ^relationship.source_attribute) == + field( + row, + ^relationship.destination_attribute + ) + ) + + relationship_destination = + %{sub | query: new_inner_query} + from([{row, current_binding}] in query, left_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, - on: - field(row, ^relationship.source_attribute) == - field( - destination, - ^relationship.destination_attribute - ) + on: true ) end From cd9a38bf2af2e2f538c7c5ffa6697e82799bedf6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 22 Feb 2024 07:31:57 -0500 Subject: [PATCH 0248/1215] chore: release version v1.5.7 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5b1155..94266491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.7](https://github.com/ash-project/ash_postgres/compare/v1.5.6...v1.5.7) (2024-02-22) + + + + +### Bug Fixes: + +* properly apply lateral join conditions to left lateral joins + ## [v1.5.6](https://github.com/ash-project/ash_postgres/compare/v1.5.5...v1.5.6) (2024-02-21) diff --git a/mix.exs b/mix.exs index b98a7780..c889b27f 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.6" + @version "1.5.7" def project do [ From 37cb3825f132422f18ccfa6c8fdba469bd2370fb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 23 Feb 2024 20:53:19 -0500 Subject: [PATCH 0249/1215] fix: properly handle complex types in lists --- .tool-versions | 4 +- lib/data_layer.ex | 6 +- lib/expr.ex | 127 ++++--- lib/types/types.ex | 37 +- mix.exs | 2 +- mix.lock | 2 +- .../test_repo/posts/20240224001913.json | 341 ++++++++++++++++++ .../20240224001913_migrate_resources16.exs | 21 ++ test/bulk_update_test.exs | 14 + test/support/resources/post.ex | 1 + 10 files changed, 490 insertions(+), 65 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20240224001913.json create mode 100644 priv/test_repo/migrations/20240224001913_migrate_resources16.exs diff --git a/.tool-versions b/.tool-versions index 0a06d5a6..b80e3611 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 26.0.2 -elixir 1.16.0 +erlang 26.2.2 +elixir 1.16.1 diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 9a4a6a29..70369aab 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2641,7 +2641,11 @@ defmodule AshPostgres.DataLayer do Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> attribute = Ash.Resource.Info.attribute(resource, field) - type = AshPostgres.Types.parameterized_type(attribute.type, attribute.constraints) + type = + AshPostgres.Types.parameterized_type( + attribute.type, + attribute.constraints + ) case AshPostgres.Expr.dynamic_expr( query, diff --git a/lib/expr.ex b/lib/expr.ex index cdcc015c..6d5c53dd 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -740,15 +740,8 @@ defmodule AshPostgres.Expr do {dynamic, acc} = do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?, acc) - type = - if is_binary(expr) do - :string - else - :any - end - {item, params, count} = - {{:^, [], [count]}, [{dynamic, type} | params], count + 1} + {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} {params, [{:expr, item} | fragment_data], count, acc} end) @@ -1237,7 +1230,14 @@ defmodule AshPostgres.Expr do if type do {expr, acc} = do_dynamic_expr(query, arg1, bindings, embedded?, acc, type) - {Ecto.Query.dynamic(type(^expr, ^type)), acc} + + case {type, expr} do + {{:parameterized, Ash.Type.Map.EctoType, []}, %Ecto.Query.DynamicExpr{}} -> + {expr, acc} + + _ -> + {Ecto.Query.dynamic(type(^expr, ^type)), acc} + end else do_dynamic_expr(query, arg1, bindings, embedded?, acc, type) end @@ -1959,45 +1959,82 @@ defmodule AshPostgres.Expr do end defp list_expr(query, value, bindings, embedded?, acc, type) do - type = - case type do - {:array, type} -> type - {:in, type} -> type - _ -> nil - end + if !Enum.empty?(value) && + Enum.any?(value, fn value -> + Ash.Filter.TemplateHelpers.expr?(value) || is_map(value) || is_list(value) + end) do + type = + case type do + {:array, type} -> type + {:in, type} -> type + _ -> nil + end + + elements = + Enum.map(value, fn list_item -> + if type do + {:expr, %Ash.Query.Function.Type{arguments: [list_item, type, []]}} + else + {:expr, list_item} + end + end) + |> Enum.intersperse({:raw, ","}) - {params, exprs, _, acc} = - Enum.reduce(value, {[], [], 0, acc}, fn value, {params, data, count, acc} -> - case do_dynamic_expr(query, value, bindings, embedded?, acc, type) do - {%Ecto.Query.DynamicExpr{} = dynamic, acc} -> - result = - Ecto.Query.Builder.Dynamic.partially_expand( - :select, - query, - dynamic, - params, - count - ) - - expr = elem(result, 0) - new_params = elem(result, 1) - new_count = result |> Tuple.to_list() |> List.last() - - {new_params, [expr | data], new_count, acc} - - {other, acc} -> - {params, [other | data], count, acc} + do_dynamic_expr( + query, + %Fragment{ + embedded?: embedded?, + arguments: + [ + raw: "ARRAY[" + ] ++ elements ++ [raw: "]"] + }, + bindings, + embedded?, + acc, + type + ) + else + type = + case type do + {:array, type} -> type + {:in, type} -> type + _ -> nil end - end) - {%Ecto.Query.DynamicExpr{ - fun: fn _query -> - {Enum.reverse(exprs), Enum.reverse(params), [], []} - end, - binding: [], - file: __ENV__.file, - line: __ENV__.line - }, acc} + {params, exprs, _, acc} = + Enum.reduce(value, {[], [], 0, acc}, fn value, {params, data, count, acc} -> + case do_dynamic_expr(query, value, bindings, embedded?, acc, type) do + {%Ecto.Query.DynamicExpr{} = dynamic, acc} -> + result = + Ecto.Query.Builder.Dynamic.partially_expand( + :select, + query, + dynamic, + params, + count + ) + + expr = elem(result, 0) + new_params = elem(result, 1) + new_count = result |> Tuple.to_list() |> List.last() + + {new_params, [expr | data], new_count, acc} + + {other, acc} -> + {params, [other | data], count, acc} + end + end) + + {%Ecto.Query.DynamicExpr{ + fun: fn _query -> + {Enum.reverse(exprs), Enum.reverse(params), [], []} + end, + binding: [], + file: __ENV__.file, + line: __ENV__.line + }, acc} + end end defp maybe_uuid_to_binary({:array, type}, value, _original_value) when is_list(value) do diff --git a/lib/types/types.ex b/lib/types/types.ex index bb9fa7b3..0a6eca43 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -3,16 +3,18 @@ defmodule AshPostgres.Types do alias Ash.Query.Ref - def parameterized_type({:parameterized, _, _} = type, _) do + def parameterized_type(type, constraints, no_maps? \\ true) + + def parameterized_type({:parameterized, _, _} = type, _, _) do type end - def parameterized_type({:in, type}, constraints) do - parameterized_type({:array, type}, constraints) + def parameterized_type({:in, type}, constraints, no_maps?) do + parameterized_type({:array, type}, constraints, no_maps?) end - def parameterized_type({:array, type}, constraints) do - case parameterized_type(type, constraints[:items] || []) do + def parameterized_type({:array, type}, constraints, _) do + case parameterized_type(type, constraints[:items] || [], false) do nil -> nil @@ -21,22 +23,27 @@ defmodule AshPostgres.Types do end end - def parameterized_type(Ash.Type.CiString, constraints) do - parameterized_type(Ash.Type.CiStringWrapper, constraints) + def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do + parameterized_type(Ash.Type.CiStringWrapper, constraints, no_maps?) end - def parameterized_type(Ash.Type.String.EctoType, constraints) do - parameterized_type(Ash.Type.StringWrapper, constraints) + def parameterized_type(Ash.Type.String.EctoType, constraints, no_maps?) do + parameterized_type(Ash.Type.StringWrapper, constraints, no_maps?) end - def parameterized_type(:tsquery, constraints) do - parameterized_type(AshPostgres.Tsquery, constraints) + def parameterized_type(:tsquery, constraints, no_maps?) do + parameterized_type(AshPostgres.Tsquery, constraints, no_maps?) end - def parameterized_type(type, _constraints) when type in [Ash.Type.Map, Ash.Type.Map.EctoType], - do: nil + def parameterized_type(type, _constraints, false) + when type in [Ash.Type.Map, Ash.Type.Map.EctoType], + do: :map + + def parameterized_type(type, _constraints, true) + when type in [Ash.Type.Map, Ash.Type.Map.EctoType], + do: nil - def parameterized_type(type, constraints) do + def parameterized_type(type, constraints, no_maps?) do if Ash.Type.ash_type?(type) do cast_in_query? = if function_exported?(Ash.Type, :cast_in_query?, 2) do @@ -55,7 +62,7 @@ defmodule AshPostgres.Types do type end - parameterized_type(type, constraints) + parameterized_type(type, constraints, no_maps?) else nil end diff --git a/mix.exs b/mix.exs index c889b27f..df8db74c 100644 --- a/mix.exs +++ b/mix.exs @@ -157,7 +157,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.19")}, + {:ash, ash_version("~> 2.19 and >= 2.19.6")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index acec0723..79cdd22d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.19.0", "9235e1efbe82b4468849416c95d43e7c2e20c3f0dfdc5413598baf275b0c7465", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "eea848622156ab294cdde6da012f0902c69d44c3df63f5ecd92b66eb3c4b3db3"}, + "ash": {:hex, :ash, "2.19.6", "af8fd9c8c02b2249dde806f0fe3016726cd83357cfbd2f401b7859072e5d15ef", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0a60e5882a2f8902d0230b418ba942f5f8a1e65e59961147896f359a65f18e87"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/priv/resource_snapshots/test_repo/posts/20240224001913.json b/priv/resource_snapshots/test_repo/posts/20240224001913.json new file mode 100644 index 00000000..cce56403 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240224001913.json @@ -0,0 +1,341 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "3867056D0D4EF4266A002FFA9A30F4382803E87BA94ED231D2DB39A77730B034", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "where": null, + "unique": true, + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240224001913_migrate_resources16.exs b/priv/test_repo/migrations/20240224001913_migrate_resources16.exs new file mode 100644 index 00000000..a9b687e1 --- /dev/null +++ b/priv/test_repo/migrations/20240224001913_migrate_resources16.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources16 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(:posts) do + add :list_of_stuff, {:array, :map} + end + end + + def down do + alter table(:posts) do + remove :list_of_stuff + end + end +end \ No newline at end of file diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 8592eb6f..0bc8206c 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -20,6 +20,20 @@ defmodule AshPostgres.BulkUpdateTest do assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff")) end + test "a map can be given as input" do + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Api.bulk_update!( + :update, + %{list_of_stuff: [%{a: 1}]}, + return_records?: true, + strategy: [:atomic] + ) + |> Map.get(:records) + |> Enum.map(& &1.list_of_stuff) + end + test "bulk updates only apply to things that the query produces" do Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 12c509d8..27b47d4f 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -136,6 +136,7 @@ defmodule AshPostgres.Test.Post do attribute(:point, AshPostgres.Test.Point) attribute(:composite_point, AshPostgres.Test.CompositePoint) attribute(:stuff, :map) + attribute(:list_of_stuff, {:array, :map}) attribute(:uniq_one, :string) attribute(:uniq_two, :string) attribute(:uniq_custom_one, :string) From c2aeb2c8ef4d12dd8d56a7188bb0ec90f1a7332b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 23 Feb 2024 20:54:08 -0500 Subject: [PATCH 0250/1215] chore: release version v1.5.8 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94266491..9f370e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.8](https://github.com/ash-project/ash_postgres/compare/v1.5.7...v1.5.8) (2024-02-24) + + + + +### Bug Fixes: + +* properly handle complex types in lists + ## [v1.5.7](https://github.com/ash-project/ash_postgres/compare/v1.5.6...v1.5.7) (2024-02-22) diff --git a/mix.exs b/mix.exs index df8db74c..8f78711a 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.7" + @version "1.5.8" def project do [ From 8fa10ac6ec11955deb83ebaba2445c792876f234 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 24 Feb 2024 17:10:01 -0500 Subject: [PATCH 0251/1215] fix: only apply filters inside aggregate subquery --- lib/data_layer.ex | 69 +++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 70369aab..29005a9d 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -753,7 +753,9 @@ defmodule AshPostgres.DataLayer do end @impl true - def run_aggregate_query(query, aggregates, resource) do + def run_aggregate_query(original_query, aggregates, resource) do + original_query = default_bindings(original_query, resource) + {can_group, cant_group} = aggregates |> Enum.split_with(&AshPostgres.Aggregate.can_group?(resource, &1)) @@ -762,37 +764,16 @@ defmodule AshPostgres.DataLayer do {can_group, cant_group} -> {can_group, cant_group} end - query = default_bindings(query, resource) - - query = - if query.distinct || query.limit do - query = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - - from(row in subquery(query), as: ^0, select: %{}) - else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) - end - - query_before_select = query - {global_filter, can_group} = AshPostgres.Aggregate.extract_shared_filters(can_group) query = case global_filter do {:ok, global_filter} -> - filter(query, global_filter, resource) + filter(original_query, global_filter, resource) :error -> - {:ok, query} + {:ok, original_query} end case query do @@ -800,6 +781,23 @@ defmodule AshPostgres.DataLayer do {:error, error} {:ok, query} -> + query = + if query.distinct || query.limit do + query = + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + + from(row in subquery(query), as: ^0, select: %{}) + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + |> Ecto.Query.select(%{}) + end + query = Enum.reduce( can_group, @@ -828,11 +826,28 @@ defmodule AshPostgres.DataLayer do dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) end - {:ok, add_single_aggs(result, resource, query_before_select, cant_group)} + {:ok, add_single_aggs(result, resource, original_query, cant_group)} end end defp add_single_aggs(result, resource, query, cant_group) do + query = + if query.distinct || query.limit do + query = + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + + from(row in subquery(query), as: ^0, select: %{}) + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + |> Ecto.Query.select(%{}) + end + Enum.reduce(cant_group, result, fn %{kind: :exists} = agg, result -> {:ok, filtered} = @@ -948,6 +963,8 @@ defmodule AshPostgres.DataLayer do {global_filter, can_group} = AshPostgres.Aggregate.extract_shared_filters(can_group) + original_subquery = subquery + subquery = case global_filter do {:ok, global_filter} -> @@ -1002,7 +1019,7 @@ defmodule AshPostgres.DataLayer do ) end - {:ok, add_single_aggs(result, source_resource, subquery, cant_group)} + {:ok, add_single_aggs(result, source_resource, original_subquery, cant_group)} end {:error, error} -> From 7c3b60b942f4af0da3c2a1b5caab3517923fe55e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 24 Feb 2024 17:20:03 -0500 Subject: [PATCH 0252/1215] fix: remove buggy global filter extraction --- lib/data_layer.ex | 181 +++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 108 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 29005a9d..673baf9f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -753,8 +753,9 @@ defmodule AshPostgres.DataLayer do end @impl true - def run_aggregate_query(original_query, aggregates, resource) do - original_query = default_bindings(original_query, resource) + def run_aggregate_query(query, aggregates, resource) do + query = default_bindings(query, resource) + original_query = query {can_group, cant_group} = aggregates @@ -764,70 +765,52 @@ defmodule AshPostgres.DataLayer do {can_group, cant_group} -> {can_group, cant_group} end - {global_filter, can_group} = - AshPostgres.Aggregate.extract_shared_filters(can_group) - query = - case global_filter do - {:ok, global_filter} -> - filter(original_query, global_filter, resource) + if query.distinct || query.limit do + query = + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) - :error -> - {:ok, original_query} + from(row in subquery(query), as: ^0, select: %{}) + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + |> Ecto.Query.select(%{}) end - case query do - {:error, error} -> - {:error, error} - - {:ok, query} -> - query = - if query.distinct || query.limit do - query = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - - from(row in subquery(query), as: ^0, select: %{}) - else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) - end + query = + Enum.reduce( + can_group, + query, + fn agg, query -> + first_relationship = + Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) - query = - Enum.reduce( - can_group, + AshPostgres.Aggregate.add_subquery_aggregate_select( query, - fn agg, query -> - first_relationship = - Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) - - AshPostgres.Aggregate.add_subquery_aggregate_select( - query, - agg.relationship_path |> Enum.drop(1), - agg, - resource, - false, - first_relationship - ) - end + agg.relationship_path |> Enum.drop(1), + agg, + resource, + false, + first_relationship ) + end + ) - result = - case can_group do - [] -> - %{} + result = + case can_group do + [] -> + %{} - _ -> - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) - end + _ -> + dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + end - {:ok, add_single_aggs(result, resource, original_query, cant_group)} - end + {:ok, add_single_aggs(result, resource, original_query, cant_group)} end defp add_single_aggs(result, resource, query, cant_group) do @@ -960,67 +943,49 @@ defmodule AshPostgres.DataLayer do subquery = from(row in subquery(lateral_join_query), as: ^0, select: %{}) subquery = default_bindings(subquery, source_resource) - {global_filter, can_group} = - AshPostgres.Aggregate.extract_shared_filters(can_group) - original_subquery = subquery - subquery = - case global_filter do - {:ok, global_filter} -> - filter(subquery, global_filter, destination_resource) - - :error -> - {:ok, subquery} - end + query = + Enum.reduce( + can_group, + subquery, + fn agg, subquery -> + has_exists? = + Ash.Filter.find(agg.query && agg.query.filter, fn + %Ash.Query.Exists{} -> true + _ -> false + end) - case subquery do - {:error, error} -> - {:error, error} + first_relationship = + Ash.Resource.Info.relationship( + source_resource, + agg.relationship_path |> Enum.at(0) + ) - {:ok, subquery} -> - query = - Enum.reduce( - can_group, + AshPostgres.Aggregate.add_subquery_aggregate_select( subquery, - fn agg, subquery -> - has_exists? = - Ash.Filter.find(agg.query && agg.query.filter, fn - %Ash.Query.Exists{} -> true - _ -> false - end) - - first_relationship = - Ash.Resource.Info.relationship( - source_resource, - agg.relationship_path |> Enum.at(0) - ) - - AshPostgres.Aggregate.add_subquery_aggregate_select( - subquery, - agg.relationship_path |> Enum.drop(1), - agg, - destination_resource, - has_exists?, - first_relationship - ) - end + agg.relationship_path |> Enum.drop(1), + agg, + destination_resource, + has_exists?, + first_relationship ) + end + ) - result = - case can_group do - [] -> - %{} + result = + case can_group do + [] -> + %{} - _ -> - dynamic_repo(source_resource, query).one( - query, - repo_opts(nil, nil, source_resource) - ) - end + _ -> + dynamic_repo(source_resource, query).one( + query, + repo_opts(nil, nil, source_resource) + ) + end - {:ok, add_single_aggs(result, source_resource, original_subquery, cant_group)} - end + {:ok, add_single_aggs(result, source_resource, original_subquery, cant_group)} {:error, error} -> {:error, error} From 98f24b1923374f635cbfdcdf0e9551ee9fec78b6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 24 Feb 2024 17:35:43 -0500 Subject: [PATCH 0253/1215] Revert "fix: remove buggy global filter extraction" This reverts commit 7c3b60b942f4af0da3c2a1b5caab3517923fe55e. --- lib/data_layer.ex | 181 +++++++++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 73 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 673baf9f..29005a9d 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -753,9 +753,8 @@ defmodule AshPostgres.DataLayer do end @impl true - def run_aggregate_query(query, aggregates, resource) do - query = default_bindings(query, resource) - original_query = query + def run_aggregate_query(original_query, aggregates, resource) do + original_query = default_bindings(original_query, resource) {can_group, cant_group} = aggregates @@ -765,52 +764,70 @@ defmodule AshPostgres.DataLayer do {can_group, cant_group} -> {can_group, cant_group} end + {global_filter, can_group} = + AshPostgres.Aggregate.extract_shared_filters(can_group) + query = - if query.distinct || query.limit do - query = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) + case global_filter do + {:ok, global_filter} -> + filter(original_query, global_filter, resource) - from(row in subquery(query), as: ^0, select: %{}) - else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) + :error -> + {:ok, original_query} end - query = - Enum.reduce( - can_group, - query, - fn agg, query -> - first_relationship = - Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + case query do + {:error, error} -> + {:error, error} - AshPostgres.Aggregate.add_subquery_aggregate_select( + {:ok, query} -> + query = + if query.distinct || query.limit do + query = + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + + from(row in subquery(query), as: ^0, select: %{}) + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + |> Ecto.Query.select(%{}) + end + + query = + Enum.reduce( + can_group, query, - agg.relationship_path |> Enum.drop(1), - agg, - resource, - false, - first_relationship + fn agg, query -> + first_relationship = + Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) + + AshPostgres.Aggregate.add_subquery_aggregate_select( + query, + agg.relationship_path |> Enum.drop(1), + agg, + resource, + false, + first_relationship + ) + end ) - end - ) - result = - case can_group do - [] -> - %{} + result = + case can_group do + [] -> + %{} - _ -> - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) - end + _ -> + dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + end - {:ok, add_single_aggs(result, resource, original_query, cant_group)} + {:ok, add_single_aggs(result, resource, original_query, cant_group)} + end end defp add_single_aggs(result, resource, query, cant_group) do @@ -943,49 +960,67 @@ defmodule AshPostgres.DataLayer do subquery = from(row in subquery(lateral_join_query), as: ^0, select: %{}) subquery = default_bindings(subquery, source_resource) + {global_filter, can_group} = + AshPostgres.Aggregate.extract_shared_filters(can_group) + original_subquery = subquery - query = - Enum.reduce( - can_group, - subquery, - fn agg, subquery -> - has_exists? = - Ash.Filter.find(agg.query && agg.query.filter, fn - %Ash.Query.Exists{} -> true - _ -> false - end) + subquery = + case global_filter do + {:ok, global_filter} -> + filter(subquery, global_filter, destination_resource) - first_relationship = - Ash.Resource.Info.relationship( - source_resource, - agg.relationship_path |> Enum.at(0) - ) + :error -> + {:ok, subquery} + end - AshPostgres.Aggregate.add_subquery_aggregate_select( + case subquery do + {:error, error} -> + {:error, error} + + {:ok, subquery} -> + query = + Enum.reduce( + can_group, subquery, - agg.relationship_path |> Enum.drop(1), - agg, - destination_resource, - has_exists?, - first_relationship + fn agg, subquery -> + has_exists? = + Ash.Filter.find(agg.query && agg.query.filter, fn + %Ash.Query.Exists{} -> true + _ -> false + end) + + first_relationship = + Ash.Resource.Info.relationship( + source_resource, + agg.relationship_path |> Enum.at(0) + ) + + AshPostgres.Aggregate.add_subquery_aggregate_select( + subquery, + agg.relationship_path |> Enum.drop(1), + agg, + destination_resource, + has_exists?, + first_relationship + ) + end ) - end - ) - result = - case can_group do - [] -> - %{} + result = + case can_group do + [] -> + %{} - _ -> - dynamic_repo(source_resource, query).one( - query, - repo_opts(nil, nil, source_resource) - ) - end + _ -> + dynamic_repo(source_resource, query).one( + query, + repo_opts(nil, nil, source_resource) + ) + end - {:ok, add_single_aggs(result, source_resource, original_subquery, cant_group)} + {:ok, add_single_aggs(result, source_resource, original_subquery, cant_group)} + end {:error, error} -> {:error, error} From f6d029d85b36c8bc76b57656b05c628923cb612e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 24 Feb 2024 18:08:00 -0500 Subject: [PATCH 0254/1215] improvement: add test for aggregates --- test/aggregate_test.exs | 16 ++++++++++++++++ test/support/resources/post.ex | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 4e7fc2e2..04b576b4 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1164,6 +1164,22 @@ defmodule AshPostgres.AggregateTest do |> Api.count!() end + test "a count with a limit and a filter can be aggregated at the query level" do + Post + |> Ash.Changeset.new(%{title: "foo"}) + |> Api.create!() + + Post + |> Ash.Changeset.new(%{title: "bar"}) + |> Api.create!() + + assert 1 == + Post + |> Ash.Query.for_read(:title_is_foo) + |> Ash.Query.limit(1) + |> Api.count!() + end + test "a count can filter independently of the query" do assert {:ok, %{count: 0, count2: 0}} = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 27b47d4f..98414de0 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -75,6 +75,10 @@ defmodule AshPostgres.Test.Post do actions do defaults([:update, :destroy]) + read :title_is_foo do + filter(expr(title == "foo")) + end + read :read do primary?(true) end From b6b2dd1dc6a6f8d4f05b3415e8c2d556fc9061df Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 24 Feb 2024 20:20:52 -0500 Subject: [PATCH 0255/1215] fix: handle more subquery filter cases for aggregates --- lib/data_layer.ex | 51 ++++++++++++++++++++++++++--------------- test/aggregate_test.exs | 4 ++-- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 29005a9d..8c8617e2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -831,23 +831,6 @@ defmodule AshPostgres.DataLayer do end defp add_single_aggs(result, resource, query, cant_group) do - query = - if query.distinct || query.limit do - query = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - - from(row in subquery(query), as: ^0, select: %{}) - else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) - end - Enum.reduce(cant_group, result, fn %{kind: :exists} = agg, result -> {:ok, filtered} = @@ -859,7 +842,22 @@ defmodule AshPostgres.DataLayer do {:ok, query} end - filtered = Ecto.Query.exclude(filtered, :distinct) + filtered = + if filtered.distinct || filtered.limit do + filtered = + filtered + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + + from(row in subquery(filtered), as: ^0, select: %{}) + else + filtered + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + |> Ecto.Query.select(%{}) + end Map.put( result || %{}, @@ -904,6 +902,23 @@ defmodule AshPostgres.DataLayer do filtered end + filtered = + if filtered.limit do + filtered = + filtered + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + + from(row in subquery(filtered), as: ^0, select: %{}) + else + filtered + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + |> Map.put(:windows, []) + |> Ecto.Query.select(%{}) + end + first_relationship = Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 04b576b4..d633bc42 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1170,10 +1170,10 @@ defmodule AshPostgres.AggregateTest do |> Api.create!() Post - |> Ash.Changeset.new(%{title: "bar"}) + |> Ash.Changeset.new(%{title: "foo"}) |> Api.create!() - assert 1 == + assert 1 = Post |> Ash.Query.for_read(:title_is_foo) |> Ash.Query.limit(1) From f5f84321f0bb5b727fde975677a67fe5250b270c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 25 Feb 2024 08:25:25 -0500 Subject: [PATCH 0256/1215] chore: release version v1.5.9 --- CHANGELOG.md | 17 +++++++++++++++++ mix.exs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f370e3f..a0abc803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.9](https://github.com/ash-project/ash_postgres/compare/v1.5.8...v1.5.9) (2024-02-25) + + + + +### Bug Fixes: + +* handle more subquery filter cases for aggregates + +* remove buggy global filter extraction + +* only apply filters inside aggregate subquery + +### Improvements: + +* add test for aggregates + ## [v1.5.8](https://github.com/ash-project/ash_postgres/compare/v1.5.7...v1.5.8) (2024-02-24) diff --git a/mix.exs b/mix.exs index 8f78711a..23000d27 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.8" + @version "1.5.9" def project do [ From 68c72dd21eee1500c86b6a84e2e787987e26f1ab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 25 Feb 2024 08:25:57 -0500 Subject: [PATCH 0257/1215] chore: update changelog --- CHANGELOG.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0abc803..24042f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,6 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline * handle more subquery filter cases for aggregates -* remove buggy global filter extraction - * only apply filters inside aggregate subquery ### Improvements: @@ -332,7 +330,7 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline * allow specifying multi-column foreign keys (#180) -* add match_with option on references +* add match_with option on references * add match_type option on references @@ -590,9 +588,9 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline * custom-extension implementation (#162) -* custom-extension implementation +* custom-extension implementation -* allow adding custom-extension by module's reference and fixes formatting +* allow adding custom-extension by module's reference and fixes formatting * support new `from_many?` option From 6455bef6ed195a48adf8fa4c8bd530543c437f14 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 25 Feb 2024 21:35:33 -0500 Subject: [PATCH 0258/1215] fix: ensure select is applied (or not) properly in bulk update/destroys --- lib/data_layer.ex | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 8c8617e2..726554fb 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1406,15 +1406,6 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> try do - query = - if options[:return_records?] do - attrs = resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) - - Ecto.Query.select(query, ^attrs) - else - query - end - repo = dynamic_repo(resource, changeset) repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) @@ -1438,6 +1429,15 @@ defmodule AshPostgres.DataLayer do end {:ok, query} -> + query = + if options[:return_records?] do + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + else + Ecto.Query.exclude(query, :select) + end + {_, results} = with_savepoint(repo, query, fn -> repo.update_all( @@ -1578,9 +1578,6 @@ defmodule AshPostgres.DataLayer do repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - repo_opts = - Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - repo = dynamic_repo(resource, changeset) query = @@ -1612,6 +1609,16 @@ defmodule AshPostgres.DataLayer do Ecto.Query.exclude(query, :order_by) end + query = + if options[:return_records?] do + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + else + query + |> Ecto.Query.exclude(:select) + end + {_, results} = with_savepoint(repo, query, fn -> repo.delete_all( From 480eadccf1d8859058f9cba57226f48707baf7e7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Feb 2024 15:46:29 -0500 Subject: [PATCH 0259/1215] fix: fix error when encoding vectors --- lib/extensions/vector.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/extensions/vector.ex b/lib/extensions/vector.ex index 1d6edb80..a27a328b 100644 --- a/lib/extensions/vector.ex +++ b/lib/extensions/vector.ex @@ -30,8 +30,19 @@ defmodule AshPostgres.Extensions.Vector do [<> | data] vec -> - data = vec |> Ash.Vector.new() |> Ash.Vector.to_binary() - [<> | data] + case Ash.Vector.new(vec) do + {:ok, vector} -> + [<> | Ash.Vector.to_binary(vector)] + + {:error, error} -> + raise """ + Attempting to encode invalid vector, error: #{inspect(error)} + + Vector: + + #{inspect(vec)} + """ + end end end From 4747a709c419f06be246851a0fc94e94ce53dfdc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Feb 2024 15:46:51 -0500 Subject: [PATCH 0260/1215] chore: release version v1.5.10 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24042f8c..ec92efc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.10](https://github.com/ash-project/ash_postgres/compare/v1.5.9...v1.5.10) (2024-02-26) + + + + +### Bug Fixes: + +* fix error when encoding vectors + +* ensure select is applied (or not) properly in bulk update/destroys + ## [v1.5.9](https://github.com/ash-project/ash_postgres/compare/v1.5.8...v1.5.9) (2024-02-25) diff --git a/mix.exs b/mix.exs index 23000d27..4b40a634 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.9" + @version "1.5.10" def project do [ From 735d7a202fb7f5fd510261dfe596279b8d54f7d6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Feb 2024 15:56:46 -0500 Subject: [PATCH 0261/1215] chore: fix syntax error from unsaved file --- lib/extensions/vector.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/extensions/vector.ex b/lib/extensions/vector.ex index a27a328b..3280e629 100644 --- a/lib/extensions/vector.ex +++ b/lib/extensions/vector.ex @@ -32,7 +32,8 @@ defmodule AshPostgres.Extensions.Vector do vec -> case Ash.Vector.new(vec) do {:ok, vector} -> - [<> | Ash.Vector.to_binary(vector)] + data = Ash.Vector.to_binary(vector) + [<> | data] {:error, error} -> raise """ From b44d8cdc551fc7aa8b7fa60932e26157d0c2b728 Mon Sep 17 00:00:00 2001 From: Robert Graff Date: Tue, 27 Feb 2024 17:23:18 -0800 Subject: [PATCH 0262/1215] Fix: migration generator extensions in multiple repos (#214) * Fix: Migration generator for extensions in multiple repos * Do not rename legacy file on dry_run --- lib/migration_generator/migration_generator.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 210dd761..e34a3f90 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -43,7 +43,7 @@ defmodule AshPostgres.MigrationGenerator do snapshots = snapshots ++ tenant_snapshots_to_include_in_global repos = - snapshots + (snapshots ++ tenant_snapshots) |> Enum.map(& &1.repo) |> Enum.uniq() @@ -156,7 +156,18 @@ defmodule AshPostgres.MigrationGenerator do defp create_extension_migrations(repos, opts) do for repo <- repos do snapshot_path = snapshot_path(opts, repo) - snapshot_file = Path.join(snapshot_path, "extensions.json") + repo_name = repo_name(repo) + + legacy_snapshot_file = Path.join(snapshot_path, "extensions.json") + + snapshot_file = + snapshot_path + |> Path.join(repo_name) + |> Path.join("extensions.json") + + unless opts.dry_run do + File.rename(legacy_snapshot_file, snapshot_file) + end installed_extensions = if File.exists?(snapshot_file) do From 61045700a2f90b06d4c7a70dd0a991f53d324dd2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 28 Feb 2024 15:54:48 -0500 Subject: [PATCH 0263/1215] chore: clarify join semantics --- lib/join.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/join.ex b/lib/join.ex index ed22a479..ce05348d 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -749,7 +749,7 @@ defmodule AshPostgres.Join do relationship, query, sort?, - path, + full_path, root_bindings ) do relationship_through = From 2b3b6b0bc914992d69ef8036fa0b2ffac5d9baab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 28 Feb 2024 21:22:34 -0500 Subject: [PATCH 0264/1215] fix: properly leverage subqueries throughout relationship joining --- lib/aggregate.ex | 343 +++++++++++++++------------- lib/data_layer.ex | 31 ++- lib/expr.ex | 39 +--- lib/join.ex | 569 +++++++++++----------------------------------- 4 files changed, 345 insertions(+), 637 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 5093fdab..c9420197 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1,7 +1,6 @@ defmodule AshPostgres.Aggregate do @moduledoc false - import Ecto.Query, only: [from: 2, subquery: 1] require Ecto.Query @next_aggregate_names Enum.reduce(0..999, %{}, fn i, acc -> @@ -140,80 +139,194 @@ defmodule AshPostgres.Aggregate do [] end - with {:ok, agg_root_query, acc} <- - AshPostgres.Join.maybe_get_resource_query( - first_relationship.destination, + with {:ok, subquery} <- + AshPostgres.Join.related_subquery( first_relationship, query, - false, - [first_relationship.name], - nil, - nil, - true, - true - ), - {:ok, agg_root_query, acc} <- - apply_first_relationship_join_filters( - agg_root_query, - query, - acc, - first_relationship, - join_filters - ), - agg_root_query <- - set_in_group( - agg_root_query, - resource - ), - {:ok, joined} <- - join_all_relationships( - agg_root_query, - aggregates, - relationship_path, - first_relationship, - is_single?, - join_filters - ), - {:ok, filtered} <- - maybe_filter_subquery( - joined, - first_relationship, - relationship_path, - aggregates, - is_single?, - source_binding - ), - with_subquery_select <- - select_all_aggregates( - aggregates, - filtered, - relationship_path, - query, - is_single?, - Ash.Resource.Info.related( - first_relationship.destination, - relationship_path - ), - first_relationship + on_subquery: fn subquery -> + subquery = + subquery + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select(%{}) + + subquery = + if Map.get(first_relationship, :no_attributes?) do + subquery + else + if first_relationship.type == :many_to_many do + join_relationship_struct = + Ash.Resource.Info.relationship( + first_relationship.source, + first_relationship.join_relationship + ) + + {:ok, through} = + AshPostgres.Join.related_subquery( + join_relationship_struct, + query + ) + + field = first_relationship.source_attribute_on_join_resource + + subquery = + from(sub in subquery, + join: through in ^through, + as: ^subquery.__ash_bindings__.current, + on: + field( + through, + ^first_relationship.destination_attribute_on_join_resource + ) == + field(sub, ^first_relationship.destination_attribute), + select_merge: map(through, ^[field]), + group_by: + field( + through, + ^first_relationship.source_attribute_on_join_resource + ), + distinct: + field( + through, + ^first_relationship.source_attribute_on_join_resource + ), + where: + field( + parent_as(^source_binding), + ^first_relationship.source_attribute + ) == + field( + through, + ^first_relationship.source_attribute_on_join_resource + ) + ) + + AshPostgres.Join.set_join_prefix( + subquery, + query, + first_relationship.destination + ) + else + field = first_relationship.destination_attribute + + if Map.get(first_relationship, :manual) do + {module, opts} = first_relationship.manual + + from(row in subquery, + group_by: field(row, ^field), + select_merge: map(row, ^[field]) + ) + + subquery = + from(row in subquery, distinct: true) + + {:ok, subquery} = + module.ash_postgres_subquery( + opts, + source_binding, + subquery.__ash_bindings__.current - 1, + subquery + ) + + AshPostgres.Join.set_join_prefix( + subquery, + query, + first_relationship.destination + ) + else + from(row in subquery, + group_by: field(row, ^field), + select_merge: map(row, ^[field]), + where: + field( + parent_as(^source_binding), + ^first_relationship.source_attribute + ) == + field( + as(^0), + ^first_relationship.destination_attribute + ) + ) + end + end + end + + subquery = + AshPostgres.Join.set_join_prefix( + subquery, + query, + first_relationship.destination + ) + + {:ok, subquery, _} = + apply_first_relationship_join_filters( + subquery, + query, + %AshPostgres.Expr.ExprInfo{}, + first_relationship, + join_filters + ) + + subquery = + set_in_group( + subquery, + resource + ) + + {:ok, joined} = + join_all_relationships( + subquery, + aggregates, + relationship_path, + first_relationship, + is_single?, + join_filters + ) + + {:ok, filtered} = + maybe_filter_subquery( + joined, + first_relationship, + relationship_path, + aggregates, + is_single?, + source_binding + ) + + select_all_aggregates( + aggregates, + filtered, + relationship_path, + query, + is_single?, + Ash.Resource.Info.related( + first_relationship.destination, + relationship_path + ), + first_relationship + ) + end ), query <- join_subquery( query, - with_subquery_select, + subquery, first_relationship, relationship_path, aggregates, source_binding, root_data_path ) do - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - if select? do new_dynamics = Enum.map( aggregates, &{&1.load, &1.name, - select_dynamic(resource, query, &1, query.__ash_bindings__.current - 1)} + select_dynamic( + resource, + query, + &1, + query.__ash_bindings__.current - 1 + )} ) {:cont, {:ok, query, new_dynamics ++ dynamics}} @@ -339,8 +452,7 @@ defmodule AshPostgres.Aggregate do agg_root_query, agg_root_query, agg_root_query.__ash_bindings__, - filter, - acc + filter ) {:ok, query, acc} @@ -575,40 +687,15 @@ defmodule AshPostgres.Aggregate do defp join_subquery( query, subquery, - %{manual: {module, opts}} = first_relationship, + %{manual: {_, _}}, _relationship_path, aggregates, - source_binding, + _source_binding, root_data_path ) do - field = first_relationship.destination_attribute - - new_subquery = - from(row in subquery, distinct: true) - - new_subquery = - if Map.get(first_relationship, :no_attributes?) do - new_subquery - else - from(row in new_subquery, - group_by: field(row, ^field), - select_merge: map(row, ^[field]) - ) - end - - {:ok, subquery} = - module.ash_postgres_subquery( - opts, - source_binding, - subquery.__ash_bindings__.current - 1, - new_subquery - ) - - subquery = AshPostgres.Join.set_join_prefix(subquery, query, first_relationship.destination) - query = from(row in query, - left_lateral_join: sub in subquery(subquery_if_distinct(subquery)), + left_lateral_join: sub in ^subquery, as: ^query.__ash_bindings__.current, on: true ) @@ -626,56 +713,15 @@ defmodule AshPostgres.Aggregate do defp join_subquery( query, subquery, - %{type: :many_to_many, join_relationship: join_relationship, source: source} = - first_relationship, + %{type: :many_to_many}, _relationship_path, aggregates, - source_binding, + _source_binding, root_data_path ) do - join_relationship_struct = Ash.Resource.Info.relationship(source, join_relationship) - - {:ok, through, acc} = - AshPostgres.Join.maybe_get_resource_query( - join_relationship_struct.destination, - join_relationship_struct, - query, - false, - [join_relationship], - nil, - subquery.__ash_bindings__.current, - false, - true - ) - - field = first_relationship.source_attribute_on_join_resource - - subquery = - from(sub in subquery, - join: through in ^through, - as: ^subquery.__ash_bindings__.current, - on: - field(through, ^first_relationship.destination_attribute_on_join_resource) == - field(sub, ^first_relationship.destination_attribute), - select_merge: map(through, ^[field]), - group_by: field(through, ^first_relationship.source_attribute_on_join_resource), - distinct: field(through, ^first_relationship.source_attribute_on_join_resource), - where: - field( - parent_as(^source_binding), - ^first_relationship.source_attribute - ) == - field( - through, - ^first_relationship.source_attribute_on_join_resource - ) - ) - - subquery = AshPostgres.Join.set_join_prefix(subquery, query, first_relationship.destination) - query = from(row in query, - left_lateral_join: agg in subquery(subquery_if_distinct(subquery)), + left_lateral_join: agg in ^subquery, as: ^query.__ash_bindings__.current, on: true ) @@ -686,38 +732,21 @@ defmodule AshPostgres.Aggregate do type: :aggregate, aggregates: aggregates }) - |> AshPostgres.DataLayer.merge_expr_accumulator(acc) + |> AshPostgres.DataLayer.merge_expr_accumulator(%AshPostgres.Expr.ExprInfo{}) end defp join_subquery( query, subquery, - first_relationship, + _first_relationship, _relationship_path, aggregates, - source_binding, + _source_binding, root_data_path ) do - field = first_relationship.destination_attribute - - subquery = - if Map.get(first_relationship, :no_attributes?) do - subquery - else - from(row in subquery, - group_by: field(row, ^field), - select_merge: map(row, ^[field]), - where: - field(parent_as(^source_binding), ^first_relationship.source_attribute) == - field(as(^0), ^first_relationship.destination_attribute) - ) - end - - subquery = AshPostgres.Join.set_join_prefix(subquery, query, first_relationship.destination) - query = from(row in query, - left_lateral_join: agg in subquery(subquery_if_distinct(subquery)), + left_lateral_join: agg in ^subquery, as: ^query.__ash_bindings__.current, on: true ) @@ -742,14 +771,6 @@ defmodule AshPostgres.Aggregate do """ end - defp subquery_if_distinct(%{distinct: nil} = query), do: query - - defp subquery_if_distinct(subquery) do - from(row in subquery(subquery), - select: row - ) - end - defp select_all_aggregates( aggregates, joined, diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 726554fb..3eee0069 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -566,6 +566,7 @@ defmodule AshPostgres.DataLayer do data_layer_query = data_layer_query |> default_bindings(resource, context) + |> add_parent_bindings(context) case context[:data_layer][:lateral_join_source] do {_, [{%{resource: resource}, _, _, _} | rest]} -> @@ -584,15 +585,12 @@ defmodule AshPostgres.DataLayer do parent end - ash_bindings = - data_layer_query.__ash_bindings__ - |> Map.put(:parent_bindings, Map.put(parent.__ash_bindings__, :parent?, true)) - |> Map.put(:parent_resources, [ - parent.__ash_bindings__.resource | parent.__ash_bindings__[:parent_resources] || [] - ]) - |> Map.put(:lateral_join?, true) + query_with_ash_bindings = + data_layer_query + |> add_parent_bindings(%{data_layer: %{parent_bindings: parent.__ash_bindings__}}) + |> Map.update!(:__ash_bindings__, &Map.put(&1, :lateral_join?, true)) - {:ok, %{data_layer_query | __ash_bindings__: ash_bindings}} + {:ok, query_with_ash_bindings} _ -> ash_bindings = @@ -603,6 +601,23 @@ defmodule AshPostgres.DataLayer do end end + defp add_parent_bindings(data_layer_query, %{data_layer: %{parent_bindings: parent_bindings}}) + when not is_nil(parent_bindings) do + new_bindings = + data_layer_query.__ash_bindings__ + |> Map.put(:parent_bindings, Map.put(parent_bindings, :parent?, true)) + |> Map.put(:parent_resources, [ + parent_bindings.resource | parent_bindings[:parent_resources] || [] + ]) + |> Map.put(:lateral_join?, true) + + %{data_layer_query | __ash_bindings__: new_bindings} + end + + defp add_parent_bindings(data_layer_query, _context) do + data_layer_query + end + @impl true def offset(query, nil, _), do: query diff --git a/lib/expr.ex b/lib/expr.ex index 6d5c53dd..9d59ad9a 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1454,16 +1454,8 @@ defmodule AshPostgres.Expr do %Ash.Filter{expression: expr, resource: first_relationship.destination} |> nest_expression(rest) - {:ok, source, source_acc} = - AshPostgres.Join.maybe_get_resource_query( - first_relationship.destination, - first_relationship, - query, - false, - [first_relationship.name] - ) - - acc = merge_accumulator(acc, source_acc) + {:ok, source} = + AshPostgres.Join.related_subquery(first_relationship, query) used_aggregates = Ash.Filter.used_aggregates(filter, []) @@ -1537,31 +1529,8 @@ defmodule AshPostgres.Expr do through_relationship = Ash.Resource.Info.relationship(resource, first_relationship.join_relationship) - through_bindings = - query - |> Map.delete(:__ash_bindings__) - |> AshPostgres.DataLayer.default_bindings( - query.__ash_bindings__.resource, - query.__ash_bindings__.context - ) - |> Map.get(:__ash_bindings__) - |> Map.put(:bindings, %{ - free_binding => %{path: [], source: first_relationship.through, type: :root} - }) - - {:ok, through, through_acc} = - AshPostgres.Join.maybe_get_resource_query( - first_relationship.through, - through_relationship, - query, - false, - [first_relationship.join_relationship], - through_bindings, - nil, - false - ) - - acc = merge_accumulator(acc, through_acc) + {:ok, through} = + AshPostgres.Join.related_subquery(through_relationship, query) query = Ecto.Query.from(destination in filtered, diff --git a/lib/join.ex b/lib/join.ex index ce05348d..8f4131fe 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -221,252 +221,104 @@ defmodule AshPostgres.Join do relationship_path_to_relationships(relationship.destination, rest, [relationship | acc]) end - def maybe_get_resource_query( - resource, + def related_subquery( relationship, root_query, - sort?, - path \\ [], - bindings \\ nil, - start_binding \\ nil, - is_subquery? \\ true, - join_relationships? \\ false + opts \\ [] ) do + on_parent_expr = Keyword.get(opts, :on_parent_expr, & &1) + on_subquery = Keyword.get(opts, :on_subquery, & &1) + + with {:ok, query} <- related_query(relationship, root_query, opts) do + has_parent_expr? = !!query.__ash_bindings__.context[:data_layer][:has_parent_expr?] + + query = + if has_parent_expr? do + on_parent_expr.(query) + else + query + end + + query = on_subquery.(query) + + query = + from(row in subquery(query), as: ^0) + |> AshPostgres.DataLayer.default_bindings(relationship.destination) + |> AshPostgres.DataLayer.merge_expr_accumulator( + query.__ash_bindings__.expression_accumulator + ) + |> Map.update!( + :__ash_bindings__, + fn bindings -> + bindings + |> Map.put(:current, query.__ash_bindings__.current) + |> put_in([:context, :data_layer], %{ + has_parent_expr?: has_parent_expr? + }) + end + ) + + {:ok, query} + end + end + + def related_query(relationship, query, opts \\ []) do + sort? = Keyword.get(opts, :sort?, false) + read_action = relationship.read_action || Ash.Resource.Info.primary_action!(relationship.destination, :read).name - context = (bindings || root_query.__ash_bindings__).context + context = query.__ash_bindings__.context - resource - |> Ash.Query.new(nil, base_filter?: false) - |> Ash.Query.set_context(%{data_layer: %{start_bindings_at: start_binding}}) + relationship.destination + |> Ash.Query.new() |> Ash.Query.set_context(context) |> Ash.Query.set_context(%{data_layer: %{table: nil}}) |> Ash.Query.set_context(relationship.context) + |> Ash.Query.do_filter(relationship.filter, parent_stack: [relationship.source]) |> Ash.Query.for_read(read_action, %{}, actor: context[:private][:actor], tenant: context[:private][:tenant] ) |> Ash.Query.unset([:sort, :distinct, :select, :limit, :offset]) + |> then(fn query -> + if sort? do + Ash.Query.sort(query, relationship.sort) + else + Ash.Query.unset(query, :sort) + end + end) + |> set_has_parent_expr_context(relationship) |> case do - %{valid?: true} = query -> - ash_query = query - - initial_query = - %{ - AshPostgres.DataLayer.resource_to_query(resource, nil) - | prefix: Map.get(root_query, :prefix) - } - - initial_query = do_relationship_sort(initial_query, relationship, sort?) - - case Ash.Query.data_layer_query(query, - initial_query: initial_query - ) do - {:ok, query} -> - query = - if join_relationships? do - {:ok, related_filter} = - Ash.Filter.hydrate_refs( - relationship.filter, - %{ - resource: relationship.destination, - public?: false - } - ) - - {:ok, query} = - AshPostgres.Join.join_all_relationships(query, related_filter) - - query - else - query - end - - {query, acc} = - query - |> do_base_filter( - root_query, - ash_query, - resource, - path, - bindings, - root_query.__ash_bindings__.expression_accumulator - ) - |> do_relationship_filter( - relationship, - root_query, - ash_query, - resource, - path, - bindings, - is_subquery? - ) - - query = - if !is_subquery? || - (Enum.empty?(List.wrap(query.order_bys)) && Enum.empty?(query.joins)) do - query - else - from(row in subquery(query), as: ^0) - |> AshPostgres.DataLayer.default_bindings(relationship.destination) - |> AshPostgres.DataLayer.merge_expr_accumulator( - query.__ash_bindings__.expression_accumulator - ) - |> Map.update!( - :__ash_bindings__, - &Map.put(&1, :current, query.__ash_bindings__.current) - ) - end - - {:ok, Map.put(query, :__tenant__, Map.get(root_query, :__tenant__)), acc} + %{valid?: true} = related_query -> + Ash.Query.data_layer_query( + Ash.Query.set_context(related_query, %{ + data_layer: %{parent_bindings: query.__ash_bindings__} + }) + ) + |> case do + {:ok, ecto_query} -> + {:ok, Ecto.Query.exclude(ecto_query, :select)} {:error, error} -> {:error, error} end - query -> - {:error, query} + %{errors: errors} -> + {:error, errors} end end - defp do_relationship_sort( - query, - %{destination: destination, sort: sort, from_many?: true}, - true - ) - when sort not in [nil, []] do - query = AshPostgres.DataLayer.default_bindings(query, destination) - - {:ok, order_by, query} = - AshPostgres.Sort.sort(query, sort, query.__ash_bindings__.resource, [], 0, :return) - - from(row in query, order_by: ^order_by) - end - - defp do_relationship_sort(query, _, _), do: query - - defp do_relationship_filter({query, acc}, %{filter: nil}, _, _, _, _, _, _), do: {query, acc} - - defp do_relationship_filter( - {query, acc}, - relationship, - root_query, - ash_query, - resource, - path, - bindings, - is_subquery? - ) do - filter = - resource - |> Ash.Filter.parse!( - relationship.filter, - ash_query.aggregates, - ash_query.calculations, - Map.update( - ash_query.context, - :parent_stack, - [relationship.source], - &[&1 | relationship.source] - ) - ) - - base_bindings = bindings || query.__ash_bindings__ - - parent_binding = - case :lists.droplast(path) do - [] -> - base_bindings.bindings - |> Enum.find_value(fn {key, %{type: type}} -> - if type == :root do - key - end - end) - - path -> - get_binding( - root_query.__ash_bindings__.resource, - path, - %{query | __ash_bindings__: base_bindings}, - [ - :inner, - :left - ] - ) - end - - parent_bindings = %{ - base_bindings - | resource: relationship.source, - calculations: %{}, - parent_resources: [], - aggregate_defs: %{}, - context: relationship.context, - current: parent_binding + 1 - } - - parent_bindings = - if bindings do - Map.put(parent_bindings, :parent_is_parent_as?, !is_subquery?) - else - parent_bindings - |> Map.update!(:bindings, &Map.take(&1, [parent_binding])) - end - - has_bindings? = not is_nil(bindings) - - bindings = - base_bindings - |> Map.put(:parent_bindings, parent_bindings) - |> Map.put(:parent_resources, [ - relationship.source | parent_bindings[:parent_resources] || [] - ]) - - {dynamic, acc} = - if has_bindings? do - filter = - if is_subquery? do - Ash.Filter.move_to_relationship_path(filter, path) - else - filter - end - - AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true, acc) - else - AshPostgres.Expr.dynamic_expr(query, filter, bindings, true, acc) - end - - {from(row in query, where: ^dynamic), acc} - end - - defp do_base_filter(query, root_query, ash_query, resource, path, bindings, acc) do - case Ash.Resource.Info.base_filter(resource) do - nil -> - {query, %AshPostgres.Expr.ExprInfo{}} - - filter -> - filter = - resource - |> Ash.Filter.parse!( - filter, - ash_query.aggregates, - ash_query.calculations, - ash_query.context - ) - - {dynamic, acc} = - if bindings do - filter = Ash.Filter.move_to_relationship_path(filter, path) - - AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true, acc) - else - AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__, true, acc) - end + defp set_has_parent_expr_context(query, relationship) do + has_parent_expr? = + Ash.Actions.Read.Relationships.has_parent_expr?(%{ + relationship + | filter: query.filter, + sort: query.sort + }) - {from(row in query, where: ^dynamic), acc} - end + Ash.Query.set_context(query, %{data_layer: %{has_parent_expr?: has_parent_expr?}}) end def set_join_prefix(join_query, query, resource) do @@ -591,27 +443,13 @@ defmodule AshPostgres.Join do used_aggregates = Ash.Filter.used_aggregates(filter, full_path) - use_root_query_bindings? = Enum.empty?(used_aggregates) - - root_bindings = - if use_root_query_bindings? do - query.__ash_bindings__ - end - - with {:ok, relationship_destination, acc} <- - maybe_get_resource_query( - relationship.destination, - relationship, - query, - sort?, - full_path, - root_bindings - ) do + with {:ok, relationship_destination} <- + related_subquery(relationship, query, sort?: sort?) do {relationship_destination, acc} = relationship_destination |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) - |> maybe_apply_filter(query, root_bindings, apply_filter, acc) + |> maybe_apply_filter(query, query.__ash_bindings__, apply_filter) query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) @@ -634,20 +472,6 @@ defmodule AshPostgres.Join do end end) - needs_subquery? = - used_aggregates != [] || Map.get(relationship, :from_many?) - - relationship_destination = - if needs_subquery? do - if Map.get(relationship, :from_many?) do - subquery(from(row in relationship_destination, limit: 1)) - else - subquery(relationship_destination) - end - else - relationship_destination - end - case module.ash_postgres_join( query, opts, @@ -665,6 +489,9 @@ defmodule AshPostgres.Join do initial_ash_bindings.current, {query.__ash_bindings__.resource, full_path} ) + + {:error, query} -> + {:error, query} end end rescue @@ -700,6 +527,8 @@ defmodule AshPostgres.Join do binding_data = %{type: kind, path: full_path, source: source} + used_aggregates = Ash.Filter.used_aggregates(filter, full_path) + query = query |> AshPostgres.DataLayer.add_binding(%{ @@ -709,49 +538,9 @@ defmodule AshPostgres.Join do }) |> AshPostgres.DataLayer.add_binding(binding_data) - {:ok, related_filter} = - Ash.Filter.hydrate_refs( - relationship.filter, - %{ - resource: relationship.destination, - aggregates: %{}, - calculations: %{}, - public?: false - } - ) - - related_filter = - Ash.Filter.move_to_relationship_path(related_filter, full_path) - - {:ok, query} = join_all_relationships(query, related_filter) - - used_aggregates = Ash.Filter.used_aggregates(filter, full_path) - - use_root_query_bindings? = Enum.empty?(used_aggregates) - - root_bindings = - if use_root_query_bindings? do - query.__ash_bindings__ - end - - with {:ok, relationship_through, through_acc} <- - maybe_get_resource_query( - relationship.through, - join_relationship, - query, - false, - join_path, - root_bindings - ), - {:ok, relationship_destination, dest_acc} <- - maybe_get_resource_query( - relationship.destination, - relationship, - query, - sort?, - full_path, - root_bindings - ) do + with {:ok, relationship_through} <- related_subquery(join_relationship, query), + {:ok, relationship_destination} <- + related_subquery(relationship, query, sort?: sort?) do relationship_through = relationship_through |> Ecto.Queryable.to_query() @@ -761,11 +550,10 @@ defmodule AshPostgres.Join do relationship_destination |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) - |> maybe_apply_filter(query, root_bindings, apply_filter, dest_acc) + |> maybe_apply_filter(query, query.__ash_bindings__, apply_filter) query = query - |> AshPostgres.DataLayer.merge_expr_accumulator(through_acc) |> AshPostgres.DataLayer.merge_expr_accumulator(dest_acc) binding_kinds = @@ -787,24 +575,14 @@ defmodule AshPostgres.Join do end end) - needs_subquery? = - used_aggregates != [] - - relationship_destination = - if needs_subquery? do - subquery(relationship_destination) - else - relationship_destination - end - query = case kind do :inner -> - from([{row, current_binding}] in query, + from(_ in query, join: through in ^relationship_through, as: ^initial_ash_bindings.current, on: - field(row, ^relationship.source_attribute) == + field(as(^current_binding), ^relationship.source_attribute) == field(through, ^relationship.source_attribute_on_join_resource), join: destination in ^relationship_destination, as: ^(initial_ash_bindings.current + 1), @@ -814,11 +592,11 @@ defmodule AshPostgres.Join do ) _ -> - from([{row, current_binding}] in query, + from(_ in query, left_join: through in ^relationship_through, as: ^initial_ash_bindings.current, on: - field(row, ^relationship.source_attribute) == + field(as(^current_binding), ^relationship.source_attribute) == field(through, ^relationship.source_attribute_on_join_resource), left_join: destination in ^relationship_destination, as: ^(initial_ash_bindings.current + 1), @@ -856,122 +634,76 @@ defmodule AshPostgres.Join do query = AshPostgres.DataLayer.add_binding(query, binding_data) - {:ok, related_filter} = - Ash.Filter.hydrate_refs( - relationship.filter, - %{ - resource: relationship.destination, - public?: false - } - ) - - related_filter = - Ash.Filter.move_to_relationship_path(related_filter, full_path) - - {:ok, query} = join_all_relationships(query, related_filter) - used_aggregates = Ash.Filter.used_aggregates(filter, full_path) - use_root_query_bindings? = Enum.empty?(used_aggregates) - needs_subquery? = - Map.get(relationship, :from_many?, false) || - (relationship.cardinality == :many && !Enum.empty?(used_aggregates)) + binding_kinds = + case kind do + :left -> + [:left, :inner] + + :inner -> + [:left, :inner] - root_bindings = - if use_root_query_bindings? && !needs_subquery? do - query.__ash_bindings__ + other -> + [other] end - case maybe_get_resource_query( - relationship.destination, - relationship, - query, - sort?, - full_path, - root_bindings, - nil, - use_root_query_bindings? && !needs_subquery? + current_binding = + Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> + if data.type in binding_kinds && data.path == path do + binding + end + end) + + case related_subquery(relationship, query, + sort?: sort?, + on_parent_reference: fn subquery -> + from(row in subquery, + where: + field(parent_as(^current_binding), ^relationship.source_attribute) == + field( + row, + ^relationship.destination_attribute + ) + ) + end ) do {:error, error} -> {:error, error} - {:ok, relationship_destination, acc} -> + {:ok, relationship_destination} -> {relationship_destination, acc} = relationship_destination - |> Ecto.Queryable.to_query() |> set_join_prefix(query, relationship.destination) - |> maybe_apply_filter(query, root_bindings, apply_filter, acc) + |> maybe_apply_filter(query, query.__ash_bindings__, apply_filter) query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - relationship_destination = - if needs_subquery? do - if Map.get(relationship, :from_many?) do - subquery(from(row in relationship_destination, limit: 1)) - else - subquery(relationship_destination) - end - else - relationship_destination - end - - binding_kinds = - case kind do - :left -> - [:left, :inner] - - :inner -> - [:left, :inner] - - other -> - [other] - end - - current_binding = - Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> - if data.type in binding_kinds && data.path == path do - binding - end - end) - - relationship_destination = - used_aggregates - |> Enum.reject(fn aggregate -> - AshPostgres.Aggregate.optimizable_first_aggregate?( - relationship.destination, - aggregate - ) - end) - |> case do - [] -> - relationship_destination - - _ -> - subquery(relationship_destination) - end - query = - case {kind, Map.get(relationship, :no_attributes?, false), needs_subquery?} do + case {kind, Map.get(relationship, :no_attributes?, false), + relationship_destination.__ash_bindings__.context[:data_layer][ + :has_parent_expr? + ]} do {:inner, true, false} -> - from([{row, current_binding}] in query, + from(_ in query, join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) {:inner, true, true} -> - from([{row, current_binding}] in query, + from(_ in query, inner_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) {:inner, false, false} -> - from([{row, current_binding}] in query, + from(_ in query, join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: - field(row, ^relationship.source_attribute) == + field(as(^current_binding), ^relationship.source_attribute) == field( destination, ^relationship.destination_attribute @@ -979,47 +711,32 @@ defmodule AshPostgres.Join do ) {:inner, false, true} -> - %Ecto.SubQuery{query: inner_query} = sub = relationship_destination - - new_inner_query = - from(row in inner_query, - where: - field(parent_as(^current_binding), ^relationship.source_attribute) == - field( - row, - ^relationship.destination_attribute - ) - ) - - relationship_destination = - %{sub | query: new_inner_query} - - from([{row, current_binding}] in query, + from(_ in query, inner_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) {:left, true, false} -> - from([{row, current_binding}] in query, + from(_ in query, left_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) {:left, true, true} -> - from([{row, current_binding}] in query, + from(_ in query, left_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true ) {:left, false, false} -> - from([{row, current_binding}] in query, + from(_ in query, left_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: - field(row, ^relationship.source_attribute) == + field(as(^current_binding), ^relationship.source_attribute) == field( destination, ^relationship.destination_attribute @@ -1027,22 +744,7 @@ defmodule AshPostgres.Join do ) {:left, false, true} -> - %Ecto.SubQuery{query: inner_query} = sub = relationship_destination - - new_inner_query = - from(row in inner_query, - where: - field(parent_as(^current_binding), ^relationship.source_attribute) == - field( - row, - ^relationship.destination_attribute - ) - ) - - relationship_destination = - %{sub | query: new_inner_query} - - from([{row, current_binding}] in query, + from(_ in query, left_lateral_join: destination in ^relationship_destination, as: ^initial_ash_bindings.current, on: true @@ -1061,10 +763,11 @@ defmodule AshPostgres.Join do end @doc false - def maybe_apply_filter(query, _root_query, _bindings, nil, acc), do: {query, acc} + def maybe_apply_filter(query, _root_query, _bindings, nil), + do: {query, %AshPostgres.Expr.ExprInfo{}} - def maybe_apply_filter(query, root_query, bindings, filter, acc) do - {dynamic, acc} = AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true, acc) + def maybe_apply_filter(query, root_query, bindings, filter) do + {dynamic, acc} = AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true) {from(row in query, where: ^dynamic), acc} end end From 23dd650100e41eba6dee4c680be0d3e479577613 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 28 Feb 2024 21:23:15 -0500 Subject: [PATCH 0265/1215] test: test improvements, regression test --- lib/aggregate.ex | 1 + .../post_followers/20240227180858.json | 85 +++++++++++++++++++ .../post_followers/20240227181137.json | 85 +++++++++++++++++++ .../20240227180858_migrate_resources17.exs | 41 +++++++++ .../20240227181137_migrate_resources18.exs | 45 ++++++++++ test/calculation_test.exs | 14 +++ test/subquery_test.exs | 3 +- test/support/registry.ex | 1 + test/support/resources/post.ex | 30 +++++++ test/support/resources/post_follower.ex | 27 ++++++ test/support/resources/user.ex | 8 ++ 11 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240227180858.json create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240227181137.json create mode 100644 priv/test_repo/migrations/20240227180858_migrate_resources17.exs create mode 100644 priv/test_repo/migrations/20240227181137_migrate_resources18.exs create mode 100644 test/support/resources/post_follower.ex diff --git a/lib/aggregate.ex b/lib/aggregate.ex index c9420197..1dc342d6 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -2,6 +2,7 @@ defmodule AshPostgres.Aggregate do @moduledoc false require Ecto.Query + import Ecto.Query, only: [from: 2] @next_aggregate_names Enum.reduce(0..999, %{}, fn i, acc -> Map.put(acc, :"aggregate_#{i}", :"aggregate_#{i + 1}") diff --git a/priv/resource_snapshots/test_repo/post_followers/20240227180858.json b/priv/resource_snapshots/test_repo/post_followers/20240227180858.json new file mode 100644 index 00000000..e836138b --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240227180858.json @@ -0,0 +1,85 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "post_followers_post_id_fkey", + "table": "posts", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "post_followers_user_id_fkey", + "table": "users", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "deferrable": false, + "match_type": null, + "match_with": null, + "on_update": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "post_followers", + "hash": "FEC22A5082958637A5B8D470E36E718103BE57900FA75735A30D4D2AE2055923", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": false +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/post_followers/20240227181137.json b/priv/resource_snapshots/test_repo/post_followers/20240227181137.json new file mode 100644 index 00000000..278820cd --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240227181137.json @@ -0,0 +1,85 @@ +{ + "attributes": [ + { + "default": "fragment(\"uuid_generate_v4()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "post_followers_post_id_fkey", + "table": "posts", + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "follower_id", + "references": { + "name": "post_followers_follower_id_fkey", + "table": "users", + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + } + ], + "table": "post_followers", + "hash": "B143CF0FE750DA20BBC417283D1B23E330E5E180B0861473597B75CEDC8FA0B4", + "repo": "Elixir.AshPostgres.TestRepo", + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "identities": [], + "schema": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": false +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240227180858_migrate_resources17.exs b/priv/test_repo/migrations/20240227180858_migrate_resources17.exs new file mode 100644 index 00000000..1778a5e2 --- /dev/null +++ b/priv/test_repo/migrations/20240227180858_migrate_resources17.exs @@ -0,0 +1,41 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources17 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 + create table(:post_followers, primary_key: false) do + add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true + + add :post_id, + references(:posts, + column: :id, + name: "post_followers_post_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + + add :user_id, + references(:users, + column: :id, + name: "post_followers_user_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + end + end + + def down do + drop constraint(:post_followers, "post_followers_post_id_fkey") + + drop constraint(:post_followers, "post_followers_user_id_fkey") + + drop table(:post_followers) + end +end \ No newline at end of file diff --git a/priv/test_repo/migrations/20240227181137_migrate_resources18.exs b/priv/test_repo/migrations/20240227181137_migrate_resources18.exs new file mode 100644 index 00000000..f55f7b3c --- /dev/null +++ b/priv/test_repo/migrations/20240227181137_migrate_resources18.exs @@ -0,0 +1,45 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources18 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 + rename table(:post_followers), :user_id, to: :follower_id + + drop constraint(:post_followers, "post_followers_user_id_fkey") + + alter table(:post_followers) do + modify :follower_id, + references(:users, + column: :id, + name: "post_followers_follower_id_fkey", + type: :uuid, + prefix: "public" + ) + end + + execute( + "ALTER TABLE post_followers alter CONSTRAINT post_followers_follower_id_fkey NOT DEFERRABLE" + ) + end + + def down do + drop constraint(:post_followers, "post_followers_follower_id_fkey") + + alter table(:post_followers) do + modify :user_id, + references(:users, + column: :id, + name: "post_followers_user_id_fkey", + type: :uuid, + prefix: "public" + ) + end + + rename table(:post_followers), :follower_id, to: :user_id + end +end \ No newline at end of file diff --git a/test/calculation_test.exs b/test/calculation_test.exs index dcbc9f74..3b6344de 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -724,6 +724,20 @@ defmodule AshPostgres.CalculationTest do |> Api.read_one!() end + test "exists with a relationship that has a filtered read action works" do + post = + Post + |> Ash.Changeset.for_create(:create, %{}) + |> Api.create!() + + post_id = post.id + + assert [%{id: ^post_id}] = + Post + |> Ash.Query.filter(has_no_followers) + |> Api.read!() + end + def fred do "fred" end diff --git a/test/subquery_test.exs b/test/subquery_test.exs index 4b12a80d..1a9e69dd 100644 --- a/test/subquery_test.exs +++ b/test/subquery_test.exs @@ -13,7 +13,6 @@ defmodule AshPostgres.SubqueryTest do Through.create(%{parent_id: parent.id, child_id: child.id}) - assert {:ok, _} = - Child.read(actor: %{email: "foo@bar.com"}) + Child.read!(actor: %{email: "foo@bar.com"}) end end diff --git a/test/support/registry.ex b/test/support/registry.ex index 2d855103..9502fd66 100644 --- a/test/support/registry.ex +++ b/test/support/registry.ex @@ -18,5 +18,6 @@ defmodule AshPostgres.Test.Registry do entry(AshPostgres.Test.Entity) entry(AshPostgres.Test.TempEntity) entry(AshPostgres.Test.Record) + entry(AshPostgres.Test.PostFollower) end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 98414de0..6d140e3b 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -208,6 +208,13 @@ defmodule AshPostgres.Test.Post do destination_attribute_on_join_resource: :destination_post_id ) + many_to_many(:followers, AshPostgres.Test.User, + through: AshPostgres.Test.PostFollower, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :follower_id, + read_action: :active + ) + has_many(:views, AshPostgres.Test.PostView) end @@ -216,12 +223,35 @@ defmodule AshPostgres.Test.Post do end calculations do + calculate( + :author_has_post_with_follower_named_fred, + :boolean, + expr( + exists( + author.posts, + has_follower_named_fred + ) + ) + ) + + calculate( + :has_no_followers, + :boolean, + expr(is_nil(author.posts.followers)) + ) + calculate(:score_after_winning, :integer, expr((score || 0) + 1)) calculate(:negative_score, :integer, expr(-score)) calculate(:category_label, :ci_string, expr("(" <> category <> ")")) calculate(:score_with_score, :string, expr(score <> score)) calculate(:foo_bar_from_stuff, :string, expr(stuff[:foo][:bar])) + calculate( + :has_follower_named_fred, + :boolean, + expr(exists(followers, name == "fred")) + ) + calculate( :composite_origin, AshPostgres.Test.CompositePoint, diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex new file mode 100644 index 00000000..796bbda4 --- /dev/null +++ b/test/support/resources/post_follower.ex @@ -0,0 +1,27 @@ +defmodule AshPostgres.Test.PostFollower do + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + postgres do + table "post_followers" + repo AshPostgres.TestRepo + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + end + + relationships do + belongs_to :post, AshPostgres.Test.Post do + allow_nil?(false) + end + + belongs_to :follower, AshPostgres.Test.User do + allow_nil?(false) + end + end +end diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index 442a9c2e..453ae319 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -4,6 +4,14 @@ defmodule AshPostgres.Test.User do actions do defaults([:create, :read, :update, :destroy]) + + read :active do + filter(expr(active)) + end + end + + calculations do + calculate(:active, :boolean, expr(is_active == true)) end attributes do From f716e3bb69d7985eb464bd0a0f4d5c191ca04203 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 28 Feb 2024 22:55:53 -0500 Subject: [PATCH 0266/1215] fix: simplify(and fix) exists subquery generation --- lib/expr.ex | 249 ++++++------------------ lib/join.ex | 39 ++-- test/support/resources/post_follower.ex | 1 + 3 files changed, 86 insertions(+), 203 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 9d59ad9a..dab06fdb 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1433,151 +1433,73 @@ defmodule AshPostgres.Expr do resource = Ash.Resource.Info.related(bindings.resource, at_path) first_relationship = Ash.Resource.Info.relationship(resource, first) - last_relationship = - Enum.reduce(rest, first_relationship, fn name, relationship -> - Ash.Resource.Info.relationship(relationship.destination, name) - end) - - {:ok, expr} = - Ash.Filter.hydrate_refs(expr, %{ - resource: last_relationship.destination, - aggregates: %{}, - parent_stack: [ + {:ok, subquery} = + AshPostgres.Join.related_subquery(first_relationship, query, + filter: Ash.Filter.move_to_relationship_path(expr, rest), + parent_resources: [ query.__ash_bindings__.resource - | query.__ash_bindings__[:parent_resource] || [] + | query.__ash_bindings__[:parent_resources] || [] ], - calculations: %{}, - public?: false - }) - - filter = - %Ash.Filter{expression: expr, resource: first_relationship.destination} - |> nest_expression(rest) - - {:ok, source} = - AshPostgres.Join.related_subquery(first_relationship, query) - - used_aggregates = Ash.Filter.used_aggregates(filter, []) - - {:ok, filtered} = - source - |> set_parent_path(query) - |> AshPostgres.Aggregate.add_aggregates( - used_aggregates, - first_relationship.destination, - false, - 0 - ) - |> case do - {:ok, query} -> - AshPostgres.DataLayer.filter( - query, - filter, - first_relationship.destination, - no_this?: true - ) - - {:error, error} -> - {:error, error} - end - - acc = merge_accumulator(query.__ash_bindings__.expression_accumulator, acc) - - free_binding = filtered.__ash_bindings__.current - - {exists_query, acc} = - cond do - Map.get(first_relationship, :manual) -> - {module, opts} = first_relationship.manual - - [pkey_attr | _] = Ash.Resource.Info.primary_key(first_relationship.destination) - - pkey_attr = Ash.Resource.Info.attribute(first_relationship.destination, pkey_attr) - - source_ref = - ref_binding( - %Ref{ - attribute: pkey_attr, - relationship_path: at_path, - resource: resource - }, - bindings - ) - - {:ok, subquery} = - module.ash_postgres_subquery( - opts, - source_ref, - 0, - filtered - ) - - {subquery, acc} - - first_relationship.type == :many_to_many -> - source_ref = - ref_binding( - %Ref{ - attribute: - Ash.Resource.Info.attribute(resource, first_relationship.source_attribute), - relationship_path: at_path, - resource: resource - }, - bindings - ) - - through_relationship = - Ash.Resource.Info.relationship(resource, first_relationship.join_relationship) - - {:ok, through} = - AshPostgres.Join.related_subquery(through_relationship, query) - - query = - Ecto.Query.from(destination in filtered, - join: through in ^through, - as: ^free_binding, - on: - field(through, ^first_relationship.destination_attribute_on_join_resource) == - field(destination, ^first_relationship.destination_attribute), - on: - field(parent_as(^source_ref), ^first_relationship.source_attribute) == - field(through, ^first_relationship.source_attribute_on_join_resource) - ) - - {query, acc} - - Map.get(first_relationship, :no_attributes?) -> - {filtered, acc} + return_subquery?: true, + on_subquery: fn subquery -> + cond do + Map.get(first_relationship, :manual) -> + subquery + + Map.get(first_relationship, :no_attributes?) -> + subquery + + first_relationship.type == :many_to_many -> + source_ref = + ref_binding( + %Ref{ + attribute: + Ash.Resource.Info.attribute(resource, first_relationship.source_attribute), + relationship_path: at_path, + resource: resource + }, + bindings + ) - true -> - source_ref = - ref_binding( - %Ref{ - attribute: - Ash.Resource.Info.attribute(resource, first_relationship.source_attribute), - relationship_path: at_path, - resource: resource - }, - bindings - ) - - query = - Ecto.Query.from(destination in filtered, - where: - field(parent_as(^source_ref), ^first_relationship.source_attribute) == - field(destination, ^first_relationship.destination_attribute) - ) - - {query, acc} - end + through_relationship = + Ash.Resource.Info.relationship(resource, first_relationship.join_relationship) + + {:ok, through} = + AshPostgres.Join.related_subquery(through_relationship, query) + + Ecto.Query.from(destination in subquery, + join: through in ^through, + as: ^subquery.__ash_bindings__.current, + on: + field(through, ^first_relationship.destination_attribute_on_join_resource) == + field(destination, ^first_relationship.destination_attribute), + on: + field(parent_as(^source_ref), ^first_relationship.source_attribute) == + field(through, ^first_relationship.source_attribute_on_join_resource) + ) + + true -> + source_ref = + ref_binding( + %Ref{ + attribute: + Ash.Resource.Info.attribute(resource, first_relationship.source_attribute), + relationship_path: at_path, + resource: resource + }, + bindings + ) - exists_query = - exists_query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select(1) - |> AshPostgres.DataLayer.set_subquery_prefix(query, first_relationship.destination) + Ecto.Query.from(destination in subquery, + where: + field(parent_as(^source_ref), ^first_relationship.source_attribute) == + field(destination, ^first_relationship.destination_attribute) + ) + end + end + ) - {Ecto.Query.dynamic(exists(Ecto.Query.subquery(exists_query))), acc} + {Ecto.Query.dynamic(exists(subquery)), acc} end defp do_dynamic_expr( @@ -2226,53 +2148,6 @@ defmodule AshPostgres.Expr do end) end - defp nest_expression(expression, relationship_path) do - case expression do - {key, value} when is_atom(key) -> - {key, nest_expression(value, relationship_path)} - - %Not{expression: expression} = not_expr -> - %{not_expr | expression: nest_expression(expression, relationship_path)} - - %BooleanExpression{left: left, right: right} = expression -> - %{ - expression - | left: nest_expression(left, relationship_path), - right: nest_expression(right, relationship_path) - } - - %{__operator__?: true, left: left, right: right} = op -> - left = nest_expression(left, relationship_path) - right = nest_expression(right, relationship_path) - %{op | left: left, right: right} - - %Ref{} = ref -> - add_to_ref_path(ref, relationship_path) - - %{__function__?: true, arguments: args} = func -> - %{func | arguments: Enum.map(args, &nest_expression(&1, relationship_path))} - - %Ash.Query.Exists{} = exists -> - %{exists | at_path: relationship_path ++ exists.at_path} - - %Ash.Query.Parent{} = parent -> - parent - - %Ash.Query.Call{args: args} = call -> - %{call | args: Enum.map(args, &nest_expression(&1, relationship_path))} - - %Ash.Filter{expression: expression} = filter -> - %{filter | expression: nest_expression(expression, relationship_path)} - - other -> - other - end - end - - defp add_to_ref_path(%Ref{relationship_path: relationship_path} = ref, to_add) do - %{ref | relationship_path: to_add ++ relationship_path} - end - @doc false def merge_accumulator(%ExprInfo{has_error?: left_has_error?}, %ExprInfo{ has_error?: right_has_error? diff --git a/lib/join.ex b/lib/join.ex index 8f4131fe..b5e5a0e0 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -242,21 +242,25 @@ defmodule AshPostgres.Join do query = on_subquery.(query) query = - from(row in subquery(query), as: ^0) - |> AshPostgres.DataLayer.default_bindings(relationship.destination) - |> AshPostgres.DataLayer.merge_expr_accumulator( - query.__ash_bindings__.expression_accumulator - ) - |> Map.update!( - :__ash_bindings__, - fn bindings -> - bindings - |> Map.put(:current, query.__ash_bindings__.current) - |> put_in([:context, :data_layer], %{ - has_parent_expr?: has_parent_expr? - }) - end - ) + if opts[:return_subquery?] do + subquery(query) + else + from(row in subquery(query), as: ^0) + |> AshPostgres.DataLayer.default_bindings(relationship.destination) + |> AshPostgres.DataLayer.merge_expr_accumulator( + query.__ash_bindings__.expression_accumulator + ) + |> Map.update!( + :__ash_bindings__, + fn bindings -> + bindings + |> Map.put(:current, query.__ash_bindings__.current) + |> put_in([:context, :data_layer], %{ + has_parent_expr?: has_parent_expr? + }) + end + ) + end {:ok, query} end @@ -264,6 +268,8 @@ defmodule AshPostgres.Join do def related_query(relationship, query, opts \\ []) do sort? = Keyword.get(opts, :sort?, false) + filter = Keyword.get(opts, :filter, nil) + parent_resources = Keyword.get(opts, :parent_stack, [relationship.source]) read_action = relationship.read_action || @@ -276,7 +282,8 @@ defmodule AshPostgres.Join do |> Ash.Query.set_context(context) |> Ash.Query.set_context(%{data_layer: %{table: nil}}) |> Ash.Query.set_context(relationship.context) - |> Ash.Query.do_filter(relationship.filter, parent_stack: [relationship.source]) + |> Ash.Query.do_filter(relationship.filter, parent_stack: parent_resources) + |> Ash.Query.do_filter(filter, parent_stack: parent_resources) |> Ash.Query.for_read(read_action, %{}, actor: context[:private][:actor], tenant: context[:private][:tenant] diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex index 796bbda4..850c75e5 100644 --- a/test/support/resources/post_follower.ex +++ b/test/support/resources/post_follower.ex @@ -1,4 +1,5 @@ defmodule AshPostgres.Test.PostFollower do + @moduledoc false use Ash.Resource, data_layer: AshPostgres.DataLayer From bc9718d0638a4ab7493afc1442c60a9148f42391 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 28 Feb 2024 23:42:48 -0500 Subject: [PATCH 0267/1215] chore: fix dialyzer --- lib/aggregate.ex | 11 ++++++++--- lib/join.ex | 31 +++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 1dc342d6..c1d71acc 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -145,6 +145,8 @@ defmodule AshPostgres.Aggregate do first_relationship, query, on_subquery: fn subquery -> + current_binding = subquery.__ash_bindings__.current + subquery = subquery |> Ecto.Query.exclude(:select) @@ -172,7 +174,7 @@ defmodule AshPostgres.Aggregate do subquery = from(sub in subquery, join: through in ^through, - as: ^subquery.__ash_bindings__.current, + as: ^current_binding, on: field( through, @@ -224,7 +226,7 @@ defmodule AshPostgres.Aggregate do module.ash_postgres_subquery( opts, source_binding, - subquery.__ash_bindings__.current - 1, + current_binding - 1, subquery ) @@ -839,7 +841,8 @@ defmodule AshPostgres.Aggregate do def can_group?(resource, aggregate) do can_group_kind?(aggregate, resource) && !has_exists?(aggregate) && - !references_to_many_relationships?(aggregate) + !references_to_many_relationships?(aggregate) && + !optimizable_first_aggregate?(resource, aggregate) end # We can potentially optimize this. We don't have to prevent aggregates that reference @@ -1404,6 +1407,8 @@ defmodule AshPostgres.Aggregate do defp single_path?(_, []), do: true + defp single_path?(_, [%{from_many?: true} | _]), do: false + defp single_path?(resource, [relationship | rest]) do relationship = Ash.Resource.Info.relationship(resource, relationship) diff --git a/lib/join.ex b/lib/join.ex index b5e5a0e0..e35def79 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -266,7 +266,7 @@ defmodule AshPostgres.Join do end end - def related_query(relationship, query, opts \\ []) do + defp related_query(relationship, query, opts) do sort? = Keyword.get(opts, :sort?, false) filter = Keyword.get(opts, :filter, nil) parent_resources = Keyword.get(opts, :parent_stack, [relationship.source]) @@ -306,7 +306,10 @@ defmodule AshPostgres.Join do ) |> case do {:ok, ecto_query} -> - {:ok, Ecto.Query.exclude(ecto_query, :select)} + {:ok, + ecto_query + |> set_join_prefix(query, relationship.destination) + |> Ecto.Query.exclude(:select)} {:error, error} -> {:error, error} @@ -453,10 +456,7 @@ defmodule AshPostgres.Join do with {:ok, relationship_destination} <- related_subquery(relationship, query, sort?: sort?) do {relationship_destination, acc} = - relationship_destination - |> Ecto.Queryable.to_query() - |> set_join_prefix(query, relationship.destination) - |> maybe_apply_filter(query, query.__ash_bindings__, apply_filter) + maybe_apply_filter(relationship_destination, query, query.__ash_bindings__, apply_filter) query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) @@ -548,16 +548,8 @@ defmodule AshPostgres.Join do with {:ok, relationship_through} <- related_subquery(join_relationship, query), {:ok, relationship_destination} <- related_subquery(relationship, query, sort?: sort?) do - relationship_through = - relationship_through - |> Ecto.Queryable.to_query() - |> set_join_prefix(query, relationship.through) - {relationship_destination, dest_acc} = - relationship_destination - |> Ecto.Queryable.to_query() - |> set_join_prefix(query, relationship.destination) - |> maybe_apply_filter(query, query.__ash_bindings__, apply_filter) + maybe_apply_filter(relationship_destination, query, query.__ash_bindings__, apply_filter) query = query @@ -680,9 +672,12 @@ defmodule AshPostgres.Join do {:ok, relationship_destination} -> {relationship_destination, acc} = - relationship_destination - |> set_join_prefix(query, relationship.destination) - |> maybe_apply_filter(query, query.__ash_bindings__, apply_filter) + maybe_apply_filter( + relationship_destination, + query, + query.__ash_bindings__, + apply_filter + ) query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) From d8baa6452753d66cb5ceee41a97e3e51f1cc5948 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 00:03:29 -0500 Subject: [PATCH 0268/1215] improvement: optimize more cases for simple join aggregates fixes #215 --- lib/aggregate.ex | 72 ++++++++++++++++++++++++++++++++++++------------ lib/expr.ex | 22 ++++++++------- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index c1d71acc..88986c90 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -569,6 +569,8 @@ defmodule AshPostgres.Aggregate do first_relationship ) + {:ok, query} = AshPostgres.Join.join_all_relationships(query, ref) + {value, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) @@ -890,15 +892,46 @@ defmodule AshPostgres.Aggregate do end @doc false - def optimizable_first_aggregate?(resource, %{ - name: name, - kind: :first, - relationship_path: relationship_path, - join_filters: join_filters - }) do - name in AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) || - (join_filters in [nil, %{}, []] && - single_path?(resource, relationship_path)) + def optimizable_first_aggregate?( + resource, + %{ + name: name, + kind: :first, + relationship_path: relationship_path, + join_filters: join_filters, + field: field + } = aggregate + ) do + resource + |> Ash.Resource.Info.related(relationship_path) + |> Ash.Resource.Info.field(field) + |> case do + %Ash.Resource.Aggregate{} -> + false + + %Ash.Resource.Calculation{} -> + field = aggregate_field(aggregate, resource) + + ref = + %Ash.Query.Ref{ + attribute: field, + relationship_path: relationship_path, + resource: resource + } + + with [] <- Ash.Filter.used_aggregates(ref, :all), + [] <- Ash.Filter.relationship_paths(ref) do + true + else + _ -> + false + end + + _ -> + name in AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) || + (join_filters in [nil, %{}, []] && + single_path?(resource, relationship_path)) + end end def optimizable_first_aggregate?(_, _), do: false @@ -1392,7 +1425,7 @@ defmodule AshPostgres.Aggregate do def aggregate_field_ref(aggregate, resource, relationship_path, query, first_relationship) do %Ash.Query.Ref{ - attribute: aggregate_field(aggregate, resource, relationship_path, query), + attribute: aggregate_field(aggregate, resource), relationship_path: relationship_path, resource: query.__ash_bindings__.resource } @@ -1417,18 +1450,21 @@ defmodule AshPostgres.Aggregate do single_path?(relationship.destination, rest) end - defp has_one_with_identity?(%{type: :has_one} = relationship) do - relationship.destination - |> Ash.Resource.Info.identities() - |> Enum.any?(fn %{keys: keys} -> - keys == [relationship.destination_attribute] - end) + defp has_one_with_identity?(%{type: :has_one, from_many?: false} = relationship) do + Ash.Resource.Info.primary_key(relationship.destination) == [ + relationship.destination_attribute + ] || + relationship.destination + |> Ash.Resource.Info.identities() + |> Enum.any?(fn %{keys: keys} -> + keys == [relationship.destination_attribute] + end) end defp has_one_with_identity?(_), do: false @doc false - def aggregate_field(aggregate, resource, _relationship_path, query) do + def aggregate_field(aggregate, resource) do case Ash.Resource.Info.field( resource, aggregate.field || List.first(Ash.Resource.Info.primary_key(resource)) @@ -1440,7 +1476,7 @@ defmodule AshPostgres.Aggregate do Map.get(calculation, :constraints, []) ) - AshPostgres.Expr.validate_type!(query, calc_type, "#{inspect(calculation.name)}") + AshPostgres.Expr.validate_type!(resource, calc_type, "#{inspect(calculation.name)}") {:ok, query_calc} = Ash.Query.Calculation.new( diff --git a/lib/expr.ex b/lib/expr.ex index dab06fdb..18b29764 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -958,7 +958,7 @@ defmodule AshPostgres.Expr do ) do {string, acc} = do_dynamic_expr(query, string, bindings, embedded?, acc) - require_extension!(query, "citext", expression) + require_extension!(query.__ash_bindings__.resource, "citext", expression) do_dynamic_expr( query, @@ -1108,9 +1108,7 @@ defmodule AshPostgres.Expr do attribute: AshPostgres.Aggregate.aggregate_field( aggregate, - Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path), - aggregate.relationship_path, - query + Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path) ), relationship_path: ref.relationship_path, resource: query.__ash_bindings__.resource @@ -1946,16 +1944,20 @@ defmodule AshPostgres.Expr do defp maybe_uuid_to_binary(_type, _value, original_value), do: original_value @doc false - def validate_type!(query, type, context) do + def validate_type!(%{__ash_bindings__: %{resource: resource}}, type, context) do + validate_type!(resource, type, context) + end + + def validate_type!(resource, type, context) do case type do {:parameterized, Ash.Type.CiStringWrapper.EctoType, _} -> - require_extension!(query, "citext", context) + require_extension!(resource, "citext", context) :ci_string -> - require_extension!(query, "citext", context) + require_extension!(resource, "citext", context) :citext -> - require_extension!(query, "citext", context) + require_extension!(resource, "citext", context) _ -> :ok @@ -2122,8 +2124,8 @@ defmodule AshPostgres.Expr do end end - defp require_extension!(query, extension, context) do - repo = AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate) + defp require_extension!(resource, extension, context) do + repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) unless extension in repo.installed_extensions() do raise Ash.Error.Query.InvalidExpression, From cfebfa278a7e205655d50eadcf9698f1991af4ea Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 00:07:56 -0500 Subject: [PATCH 0269/1215] chore: fix dialyzer issue --- lib/aggregate.ex | 5 +- .../test_repo/extensions.json | 10 ++ .../20240229050455_install_5_extensions.exs | 120 ++++++++++++++++++ test_snapshot_path/test_repo/extensions.json | 10 ++ 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/extensions.json create mode 100644 priv/test_repo/migrations/20240229050455_install_5_extensions.exs create mode 100644 test_snapshot_path/test_repo/extensions.json diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 88986c90..82d2ea85 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1445,8 +1445,9 @@ defmodule AshPostgres.Aggregate do defp single_path?(resource, [relationship | rest]) do relationship = Ash.Resource.Info.relationship(resource, relationship) - (relationship.type == :belongs_to || - has_one_with_identity?(relationship)) && + !Map.get(relationship, :from_many?) && + (relationship.type == :belongs_to || + has_one_with_identity?(relationship)) && single_path?(relationship.destination, rest) end diff --git a/priv/resource_snapshots/test_repo/extensions.json b/priv/resource_snapshots/test_repo/extensions.json new file mode 100644 index 00000000..e084bbff --- /dev/null +++ b/priv/resource_snapshots/test_repo/extensions.json @@ -0,0 +1,10 @@ +{ + "installed": [ + "ash-functions", + "uuid-ossp", + "pg_trgm", + "citext", + "demo-functions_v1" + ], + "ash_functions_version": 3 +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs new file mode 100644 index 00000000..3222fd88 --- /dev/null +++ b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs @@ -0,0 +1,120 @@ +defmodule AshPostgres.TestRepo.Migrations.Install5Extensions do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE($1, $2) $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS TRUE THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS NOT NULL THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) + RETURNS text[] AS $$ + DECLARE + start_index INT = 1; + end_index INT = array_length(arr, 1); + BEGIN + WHILE start_index <= end_index AND arr[start_index] = '' LOOP + start_index := start_index + 1; + END LOOP; + + WHILE end_index >= start_index AND arr[end_index] = '' LOOP + end_index := end_index - 1; + END LOOP; + + IF start_index > end_index THEN + RETURN ARRAY[]::text[]; + ELSE + RETURN arr[start_index : end_index]; + END IF; + END; $$ + LANGUAGE plpgsql + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"") + execute("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\"") + execute("CREATE EXTENSION IF NOT EXISTS \"citext\"") + + execute(""" + CREATE OR REPLACE FUNCTION ash_demo_functions() + RETURNS boolean AS $$ SELECT FALSE $$ + LANGUAGE SQL + IMMUTABLE; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute( + "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE) ash_trim_whitespace(text[])" + ) + + # execute("DROP EXTENSION IF EXISTS \"uuid-ossp\"") + # execute("DROP EXTENSION IF EXISTS \"pg_trgm\"") + # execute("DROP EXTENSION IF EXISTS \"citext\"") + execute(""" + DROP FUNCTION IF EXISTS ash_demo_functions() + """) + end +end \ No newline at end of file diff --git a/test_snapshot_path/test_repo/extensions.json b/test_snapshot_path/test_repo/extensions.json new file mode 100644 index 00000000..e084bbff --- /dev/null +++ b/test_snapshot_path/test_repo/extensions.json @@ -0,0 +1,10 @@ +{ + "installed": [ + "ash-functions", + "uuid-ossp", + "pg_trgm", + "citext", + "demo-functions_v1" + ], + "ash_functions_version": 3 +} \ No newline at end of file From a851a7d8346f4235c58fc80899d68b2d747d3595 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 00:08:07 -0500 Subject: [PATCH 0270/1215] chore: release version v1.5.11 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec92efc1..cc25b264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.11](https://github.com/ash-project/ash_postgres/compare/v1.5.10...v1.5.11) (2024-02-29) + + + + +### Bug Fixes: + +* simplify(and fix) exists subquery generation + +* properly leverage subqueries throughout relationship joining + +* migration generator extensions in multiple repos (#214) + +* Migration generator for extensions in multiple repos + +### Improvements: + +* optimize more cases for simple join aggregates + ## [v1.5.10](https://github.com/ash-project/ash_postgres/compare/v1.5.9...v1.5.10) (2024-02-26) diff --git a/mix.exs b/mix.exs index 4b40a634..7f85783e 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.10" + @version "1.5.11" def project do [ From b0cd8381c4c98007bd99d28f7ab2bcc14834db62 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 00:21:28 -0500 Subject: [PATCH 0271/1215] chore: remove unnecessary function head --- lib/aggregate.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 82d2ea85..05945873 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1440,8 +1440,6 @@ defmodule AshPostgres.Aggregate do defp single_path?(_, []), do: true - defp single_path?(_, [%{from_many?: true} | _]), do: false - defp single_path?(resource, [relationship | rest]) do relationship = Ash.Resource.Info.relationship(resource, relationship) From 2dde294c516520998d2099391b2f15790903b4a7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 00:41:08 -0500 Subject: [PATCH 0272/1215] chore: update ash in mix.lock --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 79cdd22d..3823e5d9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.19.6", "af8fd9c8c02b2249dde806f0fe3016726cd83357cfbd2f401b7859072e5d15ef", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0a60e5882a2f8902d0230b418ba942f5f8a1e65e59961147896f359a65f18e87"}, + "ash": {:hex, :ash, "2.19.10", "01a8d7f7728c95fdaefb2ca10fb7e43813ae049f45b9640a30e53c064ad2650b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c862406eb2fb8801e77897060722a690ae1e069fd82fc52b6874e2a8d0b164a0"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, @@ -36,8 +36,8 @@ "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, - "sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"}, - "spark": {:hex, :spark, "1.1.54", "54dac39403a2960f738ba5d60678d20b30de7381fb51b787b6bcb6aeabb73d9d", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "abc9a67cfb60a97d2f3c7e270fa968a2ace94f389e2741d406239d237ec6dbb1"}, + "sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"}, + "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From 39ff70ba97c6b9f938b8376b3bc8493ba4031dd5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 00:51:21 -0500 Subject: [PATCH 0273/1215] ci: remove postgres 10 from testing matrix (to see something) --- .github/workflows/elixir.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index e079cc88..de04299f 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -10,7 +10,7 @@ jobs: ash-ci: strategy: matrix: - postgres_version: ["10", "12", "14", "16"] + postgres_version: ["12", "14", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true From 12994d41b74c92636577b859336e704f5c96a54c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 01:04:06 -0500 Subject: [PATCH 0274/1215] ci: actually pass postgres-version through CI --- .github/workflows/elixir.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index de04299f..dae099d5 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -10,9 +10,10 @@ jobs: ash-ci: strategy: matrix: - postgres_version: ["12", "14", "16"] + postgres-version: ["10", "12", "14", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true + postgres-version: ${{ matrix.postgres-version }} secrets: hex_api_key: ${{ secrets.HEX_API_KEY }} From 9e0b566293b539a8f2d2ce9527079b1466eb8476 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 01:14:00 -0500 Subject: [PATCH 0275/1215] ci: don't fail fast on the matrix --- .github/workflows/elixir.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index dae099d5..29352c70 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -9,6 +9,7 @@ on: jobs: ash-ci: strategy: + fail-fast: false matrix: postgres-version: ["10", "12", "14", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main From 4db1b80eb1ec60965976edf9a3abbdfa1f97092c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 09:22:37 -0500 Subject: [PATCH 0276/1215] fix: ensure that `from_many?` joins are properly limited fix: ensure that lateral joins are properly filtered --- lib/join.ex | 33 +++++++++++++++++-------- mix.lock | 14 +++++------ priv/resource_snapshots/extensions.json | 10 -------- 3 files changed, 30 insertions(+), 27 deletions(-) delete mode 100644 priv/resource_snapshots/extensions.json diff --git a/lib/join.ex b/lib/join.ex index e35def79..4ada4821 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -230,7 +230,9 @@ defmodule AshPostgres.Join do on_subquery = Keyword.get(opts, :on_subquery, & &1) with {:ok, query} <- related_query(relationship, root_query, opts) do - has_parent_expr? = !!query.__ash_bindings__.context[:data_layer][:has_parent_expr?] + has_parent_expr? = + !!query.__ash_bindings__.context[:data_layer][:has_parent_expr?] || + not is_nil(query.limit) query = if has_parent_expr? do @@ -289,6 +291,7 @@ defmodule AshPostgres.Join do tenant: context[:private][:tenant] ) |> Ash.Query.unset([:sort, :distinct, :select, :limit, :offset]) + |> limit_from_many(relationship) |> then(fn query -> if sort? do Ash.Query.sort(query, relationship.sort) @@ -320,6 +323,12 @@ defmodule AshPostgres.Join do end end + defp limit_from_many(query, %{from_many?: true}) do + Ash.Query.limit(query, 1) + end + + defp limit_from_many(query, _), do: query + defp set_has_parent_expr_context(query, relationship) do has_parent_expr? = Ash.Actions.Read.Relationships.has_parent_expr?(%{ @@ -656,15 +665,19 @@ defmodule AshPostgres.Join do case related_subquery(relationship, query, sort?: sort?, - on_parent_reference: fn subquery -> - from(row in subquery, - where: - field(parent_as(^current_binding), ^relationship.source_attribute) == - field( - row, - ^relationship.destination_attribute - ) - ) + on_parent_expr: fn subquery -> + if Map.get(relationship, :no_attributes?) do + subquery + else + from(row in subquery, + where: + field(parent_as(^current_binding), ^relationship.source_attribute) == + field( + row, + ^relationship.destination_attribute + ) + ) + end end ) do {:error, error} -> diff --git a/mix.lock b/mix.lock index 3823e5d9..cc254a54 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,10 @@ %{ "ash": {:hex, :ash, "2.19.10", "01a8d7f7728c95fdaefb2ca10fb7e43813ae049f45b9640a30e53c064ad2650b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c862406eb2fb8801e77897060722a690ae1e069fd82fc52b6874e2a8d0b164a0"}, - "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, + "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, - "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, + "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, @@ -16,12 +16,12 @@ "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, - "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, + "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, - "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, + "excoveralls": {:hex, :excoveralls, "0.18.0", "b92497e69465dc51bc37a6422226ee690ab437e4c06877e836f1c18daeb35da9", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1109bb911f3cb583401760be49c02cbbd16aed66ea9509fc5479335d284da60b"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.5.5", "4f8369f3c9347e06a7f289de98fadfc95194149156335c5292479a53eddbccd2", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3b1e3b12968f9da6f79b5e2b2274477206949376e3579d05a5f3d439eda0b746"}, + "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -31,11 +31,11 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, - "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, + "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"}, "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, diff --git a/priv/resource_snapshots/extensions.json b/priv/resource_snapshots/extensions.json deleted file mode 100644 index e084bbff..00000000 --- a/priv/resource_snapshots/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "installed": [ - "ash-functions", - "uuid-ossp", - "pg_trgm", - "citext", - "demo-functions_v1" - ], - "ash_functions_version": 3 -} \ No newline at end of file From 2c8c993cf92ca56f45c72d29397f2608c0533405 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 13:01:53 -0500 Subject: [PATCH 0277/1215] chore: optimization/cleanups --- lib/aggregate.ex | 19 +++++++++++++++++-- lib/join.ex | 2 +- mix.lock | 8 -------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 05945873..1582d8ca 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -30,7 +30,14 @@ defmodule AshPostgres.Aggregate do {query, []}, fn aggregate, {query, aggregates} -> if is_atom(aggregate.name) do - {query, [aggregate | aggregates]} + existing_agg = query.__ash_bindings__.aggregate_defs[aggregate.name] + + if existing_agg && different_queries?(existing_agg.query, aggregate.query) do + {query, name} = use_aggregate_name(query, aggregate.name) + {query, [%{aggregate | name: name} | aggregates]} + else + {query, [aggregate | aggregates]} + end else {query, name} = use_aggregate_name(query, aggregate.name) @@ -56,7 +63,7 @@ defmodule AshPostgres.Aggregate do result = aggregates |> Enum.reject(&already_added?(&1, query.__ash_bindings__)) - |> Enum.group_by(&{&1.relationship_path, &1.join_filters}) + |> Enum.group_by(&{&1.relationship_path, &1.join_filters || %{}}) |> Enum.flat_map(fn {{path, join_filters}, aggregates} -> {can_group, cant_group} = Enum.split_with(aggregates, &can_group?(resource, &1)) @@ -373,6 +380,14 @@ defmodule AshPostgres.Aggregate do ) end + defp different_queries?(nil, nil), do: false + defp different_queries?(nil, _), do: true + defp different_queries?(_, nil), do: true + + defp different_queries?(query1, query2) do + query1.filter != query2.filter && query1.sort != query2.sort + end + @doc false def extract_shared_filters(aggregates) do aggregates diff --git a/lib/join.ex b/lib/join.ex index 4ada4821..420772f1 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -433,7 +433,7 @@ defmodule AshPostgres.Join do defp add_distinct(relationship, _join_type, joined_query) do if !joined_query.__ash_bindings__.in_group? && - (relationship.cardinality == :many || Map.get(relationship, :from_many?)) && + relationship.cardinality == :many && !joined_query.distinct do from(row in joined_query, distinct: ^Ash.Resource.Info.primary_key(joined_query.__ash_bindings__.resource) diff --git a/mix.lock b/mix.lock index cc254a54..8c9cd596 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,6 @@ "ash": {:hex, :ash, "2.19.10", "01a8d7f7728c95fdaefb2ca10fb7e43813ae049f45b9640a30e53c064ad2650b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c862406eb2fb8801e77897060722a690ae1e069fd82fc52b6874e2a8d0b164a0"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, @@ -22,26 +21,19 @@ "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"}, "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } From 74a03d3cc5c8fa4f97d607c4bbc3930a3b643990 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 13:24:05 -0500 Subject: [PATCH 0278/1215] chore: release version v1.5.12 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc25b264..58ce1916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.12](https://github.com/ash-project/ash_postgres/compare/v1.5.11...v1.5.12) (2024-02-29) + + + + +### Bug Fixes: + +* ensure that `from_many?` joins are properly limited + +* ensure that lateral joins are properly filtered + ## [v1.5.11](https://github.com/ash-project/ash_postgres/compare/v1.5.10...v1.5.11) (2024-02-29) diff --git a/mix.exs b/mix.exs index 7f85783e..661217df 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.11" + @version "1.5.12" def project do [ From 43deb00e36c3a39e0c6dafc2963367485c4cc6b2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 13:42:42 -0500 Subject: [PATCH 0279/1215] fix: properly handle multiple sorts in aggregate --- lib/sort.ex | 43 ++++++++++++++++++++++++++-------- test/support/resources/post.ex | 2 +- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/sort.ex b/lib/sort.ex index fa345263..4858949d 100644 --- a/lib/sort.ex +++ b/lib/sort.ex @@ -254,30 +254,53 @@ defmodule AshPostgres.Sort do def order_to_fragments([]), do: [] - def order_to_fragments(order) when is_list(order) do - Enum.map(order, &do_order_to_fragments(&1)) + def order_to_fragments([last]) do + [do_order_to_fragments(last, false)] end - def do_order_to_fragments({order, sort}) do - case order do - :asc -> + def order_to_fragments([first | rest]) do + [do_order_to_fragments(first, true) | order_to_fragments(rest)] + end + + def do_order_to_fragments({order, sort}, comma?) do + case {order, comma?} do + {:asc, false} -> Ecto.Query.dynamic([row], fragment("? ASC", ^sort)) - :desc -> + {:desc, false} -> Ecto.Query.dynamic([row], fragment("? DESC", ^sort)) - :asc_nulls_last -> + {:asc_nulls_last, false} -> Ecto.Query.dynamic([row], fragment("? ASC NULLS LAST", ^sort)) - :asc_nulls_first -> + {:asc_nulls_first, false} -> Ecto.Query.dynamic([row], fragment("? ASC NULLS FIRST", ^sort)) - :desc_nulls_first -> + {:desc_nulls_first, false} -> Ecto.Query.dynamic([row], fragment("? DESC NULLS FIRST", ^sort)) - :desc_nulls_last -> + {:desc_nulls_last, false} -> Ecto.Query.dynamic([row], fragment("? DESC NULLS LAST", ^sort)) "DESC NULLS LAST" + + {:asc, true} -> + Ecto.Query.dynamic([row], fragment("? ASC, ", ^sort)) + + {:desc, true} -> + Ecto.Query.dynamic([row], fragment("? DESC, ", ^sort)) + + {:asc_nulls_last, true} -> + Ecto.Query.dynamic([row], fragment("? ASC NULLS LAST, ", ^sort)) + + {:asc_nulls_first, true} -> + Ecto.Query.dynamic([row], fragment("? ASC NULLS FIRST, ", ^sort)) + + {:desc_nulls_first, true} -> + Ecto.Query.dynamic([row], fragment("? DESC NULLS FIRST, ", ^sort)) + + {:desc_nulls_last, true} -> + Ecto.Query.dynamic([row], fragment("? DESC NULLS LAST, ", ^sort)) + "DESC NULLS LAST" end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6d140e3b..684a7b92 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -379,7 +379,7 @@ defmodule AshPostgres.Test.Post do end first :last_comment, :comments, :title do - sort(title: :desc) + sort(title: :desc, title: :asc) end first(:author_first_name, :author, :first_name) From 772b2b38d58640f5da2dd0a778e7d363a1d37d1a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 13:43:08 -0500 Subject: [PATCH 0280/1215] chore: release version v1.5.13 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ce1916..4d3fab6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.13](https://github.com/ash-project/ash_postgres/compare/v1.5.12...v1.5.13) (2024-02-29) + + + + +### Bug Fixes: + +* properly handle multiple sorts in aggregate + ## [v1.5.12](https://github.com/ash-project/ash_postgres/compare/v1.5.11...v1.5.12) (2024-02-29) diff --git a/mix.exs b/mix.exs index 661217df..e6717dc1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.12" + @version "1.5.13" def project do [ From cea4b70a51cbf043d552dcbed3f0bd34a9299a33 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 20:20:33 -0500 Subject: [PATCH 0281/1215] improvement: no need for subquery for simple table aliases --- lib/join.ex | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 420772f1..1f85aedb 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -247,21 +247,25 @@ defmodule AshPostgres.Join do if opts[:return_subquery?] do subquery(query) else - from(row in subquery(query), as: ^0) - |> AshPostgres.DataLayer.default_bindings(relationship.destination) - |> AshPostgres.DataLayer.merge_expr_accumulator( - query.__ash_bindings__.expression_accumulator - ) - |> Map.update!( - :__ash_bindings__, - fn bindings -> - bindings - |> Map.put(:current, query.__ash_bindings__.current) - |> put_in([:context, :data_layer], %{ - has_parent_expr?: has_parent_expr? - }) - end - ) + if Enum.empty?(query.joins) && Enum.empty?(query.order_bys) && Enum.empty?(query.wheres) do + query + else + from(row in subquery(query), as: ^0) + |> AshPostgres.DataLayer.default_bindings(relationship.destination) + |> AshPostgres.DataLayer.merge_expr_accumulator( + query.__ash_bindings__.expression_accumulator + ) + |> Map.update!( + :__ash_bindings__, + fn bindings -> + bindings + |> Map.put(:current, query.__ash_bindings__.current) + |> put_in([:context, :data_layer], %{ + has_parent_expr?: has_parent_expr? + }) + end + ) + end end {:ok, query} From 782e58ef85d271081dc8c4fdf60d59163d410bfb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 Feb 2024 20:20:47 -0500 Subject: [PATCH 0282/1215] chore: release version v1.5.14 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3fab6b..491d6454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.14](https://github.com/ash-project/ash_postgres/compare/v1.5.13...v1.5.14) (2024-03-01) + + + + +### Improvements: + +* no need for subquery for simple table aliases + ## [v1.5.13](https://github.com/ash-project/ash_postgres/compare/v1.5.12...v1.5.13) (2024-02-29) diff --git a/mix.exs b/mix.exs index e6717dc1..cd82d0e8 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.13" + @version "1.5.14" def project do [ From ec122c61d6a99cf6f30ec6c4a5de4b0401201a8b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 1 Mar 2024 13:06:14 -0500 Subject: [PATCH 0283/1215] improvement: don't double cast to the same type improvement: detect more types --- lib/expr.ex | 17 +++++++++++++++++ lib/types/types.ex | 45 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 18b29764..31d98934 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1214,6 +1214,23 @@ defmodule AshPostgres.Expr do do_dynamic_expr(query, frag, bindings, pred_embedded? || embedded?, acc) end + defp do_dynamic_expr( + query, + %Type{ + arguments: [ + %Type{arguments: [_, type, constraints]} = nested_call, + type, + constraints + ] + }, + bindings, + embedded?, + acc, + type + ) do + do_dynamic_expr(query, nested_call, bindings, embedded?, acc, type) + end + defp do_dynamic_expr( query, %Type{arguments: [arg1, arg2, constraints]}, diff --git a/lib/types/types.ex b/lib/types/types.ex index 0a6eca43..ca0d9c39 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -189,13 +189,32 @@ defmodule AshPostgres.Types do end defp fill_in_known_type( - {vague_type, %Ref{attribute: %{type: type, constraints: constraints}}} = ref + {{:array, type}, + %Ash.Query.Function.Type{arguments: [inner, {:array, type}, constraints]} = func} + ) do + {:in, + fill_in_known_type({type, %{func | arguments: [inner, type, constraints[:items] || []]}})} + end + + defp fill_in_known_type( + {{:array, type}, + %Ref{attribute: %{type: {:array, type}, constraints: constraints} = attribute} = ref} + ) do + {:in, + fill_in_known_type( + {type, + %{ref | attribute: %{attribute | type: type, constraints: constraints[:items] || []}}} + )} + end + + defp fill_in_known_type( + {vague_type, %Ash.Query.Function.Type{arguments: [_, type, constraints]}} = func ) when vague_type in [:any, :same] do if Ash.Type.ash_type?(type) do type = type |> parameterized_type(constraints) |> array_to_in() - {type || :any, ref} + {type || :any, func} else type = if is_atom(type) && :erlang.function_exported(type, :type, 1) do @@ -204,14 +223,28 @@ defmodule AshPostgres.Types do type |> array_to_in() end - {type, ref} + {type, func} end end defp fill_in_known_type( - {{:array, type}, %Ref{attribute: %{type: {:array, type}} = attribute} = ref} - ) do - {:in, fill_in_known_type({type, %{ref | attribute: %{attribute | type: type}}})} + {vague_type, %Ref{attribute: %{type: type, constraints: constraints}}} = ref + ) + when vague_type in [:any, :same] do + if Ash.Type.ash_type?(type) do + type = type |> parameterized_type(constraints) |> array_to_in() + + {type || :any, ref} + else + type = + if is_atom(type) && :erlang.function_exported(type, :type, 1) do + {:parameterized, type, []} |> array_to_in() + else + type |> array_to_in() + end + + {type, ref} + end end defp fill_in_known_type({type, value}), do: {array_to_in(type), value} From 6b87b5e7d76bcac7a962973bdb3328f10c53a6b5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 1 Mar 2024 13:06:42 -0500 Subject: [PATCH 0284/1215] chore: release version v1.5.15 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 491d6454..665d96d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.15](https://github.com/ash-project/ash_postgres/compare/v1.5.14...v1.5.15) (2024-03-01) + + + + +### Improvements: + +* don't double cast to the same type + +* detect more types + ## [v1.5.14](https://github.com/ash-project/ash_postgres/compare/v1.5.13...v1.5.14) (2024-03-01) diff --git a/mix.exs b/mix.exs index cd82d0e8..50e085ad 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.14" + @version "1.5.15" def project do [ From fce0fefe72aaaa528dd7f2408dcf985895f7f7c4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Mar 2024 00:10:57 -0500 Subject: [PATCH 0285/1215] fix: don't apply join relationship sort for lateral join fixes #218 --- lib/data_layer.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3eee0069..b044b164 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1228,7 +1228,6 @@ defmodule AshPostgres.DataLayer do |> Ash.Query.new() |> Ash.Query.set_context(through_relationship.context) |> Ash.Query.do_filter(through_relationship.filter) - |> Ash.Query.sort(through_relationship.sort, prepend?: true) |> Ash.Query.set_tenant(source_query.tenant) |> Ash.Query.put_context(:data_layer, %{ start_bindings_at: query.__ash_bindings__.current From 70af9ee5bdb05a27466a39b21baadfef0da950a1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 5 Mar 2024 09:19:55 -0500 Subject: [PATCH 0286/1215] fix: always exclude `:order_by` on bulk updateable query --- lib/data_layer.ex | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b044b164..31c92f29 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1493,7 +1493,7 @@ defmodule AshPostgres.DataLayer do ), {:ok, query} <- AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do - {:cont, {:ok, Ecto.Query.exclude(query, :order_by)}} + {:cont, {:ok, query}} else {:error, error} -> {:halt, {:error, error}} @@ -1506,19 +1506,22 @@ defmodule AshPostgres.DataLayer do {:ok, query |> default_bindings(resource, context) - |> Ecto.Query.exclude(:select)} + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by)} %{qual: :inner} -> {:ok, query |> default_bindings(resource, context) - |> Ecto.Query.exclude(:select)} + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by)} _other_type_of_join -> root_query = from(row in query.from.source, []) |> Map.put(:__ash_bindings__, query.__ash_bindings__) |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) dynamic = Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> From 1a4a508e3979ab92e93c4b4601b15281c957f319 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 5 Mar 2024 09:20:32 -0500 Subject: [PATCH 0287/1215] chore: release version v1.5.16 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 665d96d8..da7ac739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.16](https://github.com/ash-project/ash_postgres/compare/v1.5.15...v1.5.16) (2024-03-05) + + + + +### Bug Fixes: + +* always exclude `:order_by` on bulk updateable query + +* don't apply join relationship sort for lateral join + ## [v1.5.15](https://github.com/ash-project/ash_postgres/compare/v1.5.14...v1.5.15) (2024-03-01) diff --git a/mix.exs b/mix.exs index 50e085ad..e441f3d7 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.15" + @version "1.5.16" def project do [ From 40481a17a04711aba8f2d7be804b49227fd537ea Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 6 Mar 2024 08:23:56 -0500 Subject: [PATCH 0288/1215] fix: prevent ecto/pg from getting confused about the type of maps --- lib/expr.ex | 11 +++++++---- test/bulk_update_test.exs | 11 +++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 31d98934..431a1156 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1594,10 +1594,13 @@ defmodule AshPostgres.Expr do defp do_dynamic_expr(query, value, bindings, embedded?, acc, _type) when is_map(value) and not is_struct(value) do - Enum.reduce(value, {%{}, acc}, fn {key, value}, {map, acc} -> - {value, acc} = do_dynamic_expr(query, value, bindings, embedded?, acc) - {Map.put(map, key, value), acc} - end) + {value, acc} = + Enum.reduce(value, {%{}, acc}, fn {key, value}, {map, acc} -> + {value, acc} = do_dynamic_expr(query, value, bindings, embedded?, acc) + {Map.put(map, key, value), acc} + end) + + {Ecto.Query.dynamic([], type(^value, :map)), acc} end defp do_dynamic_expr(query, other, bindings, true, acc, type) do diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 0bc8206c..d561167b 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -34,6 +34,17 @@ defmodule AshPostgres.BulkUpdateTest do |> Enum.map(& &1.list_of_stuff) end + test "a map can be given as input on a regular update" do + %{records: [post | _]} = + Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, + return_records?: true + ) + + post + |> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{a: [:a, :b]}, %{a: [:c, :d]}]}) + |> Api.update!() + end + test "bulk updates only apply to things that the query produces" do Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) From c2aacc65db31fe428bd0c6bceebdad55806ee4c3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 6 Mar 2024 08:30:41 -0500 Subject: [PATCH 0289/1215] chore: only cast embedded maps statically --- lib/expr.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/expr.ex b/lib/expr.ex index 431a1156..8a1dce7c 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1600,7 +1600,11 @@ defmodule AshPostgres.Expr do {Map.put(map, key, value), acc} end) - {Ecto.Query.dynamic([], type(^value, :map)), acc} + if embedded? do + {Ecto.Query.dynamic([], type(^value, :map)), acc} + else + {value, acc} + end end defp do_dynamic_expr(query, other, bindings, true, acc, type) do From a03ff679f017c7937c6671fd775dcbbd76b138dc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 6 Mar 2024 08:30:56 -0500 Subject: [PATCH 0290/1215] chore: release version v1.5.17 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da7ac739..76870f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.17](https://github.com/ash-project/ash_postgres/compare/v1.5.16...v1.5.17) (2024-03-06) + + + + +### Bug Fixes: + +* prevent ecto/pg from getting confused about the type of maps + ## [v1.5.16](https://github.com/ash-project/ash_postgres/compare/v1.5.15...v1.5.16) (2024-03-05) diff --git a/mix.exs b/mix.exs index e441f3d7..c9a4855a 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.16" + @version "1.5.17" def project do [ From 603d9c8cc6eead370ab94901fed1702f08274d1b Mon Sep 17 00:00:00 2001 From: Jechol Lee Date: Thu, 7 Mar 2024 09:33:10 +0900 Subject: [PATCH 0291/1215] fix: merge base_filter and custom index's where correctly (#219) --- lib/migration_generator/operation.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 2a2b6f13..29db69f3 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -893,10 +893,10 @@ defmodule AshPostgres.MigrationGenerator.Operation do end index = - if index.where && base_filter do - %{index | where: base_filter <> " AND " <> index.where} - else - index + case {index.where, base_filter} do + {_where, nil} -> index + {nil, base_filter} -> %{index | where: base_filter} + {where, base_filter} -> %{index | where: base_filter <> " AND " <> where} end opts = From 842f16a2fa4b86f7d77db590557576b0f523bd13 Mon Sep 17 00:00:00 2001 From: Robert Graff Date: Wed, 6 Mar 2024 20:54:08 -0800 Subject: [PATCH 0292/1215] fix: typo in extension generator creates invalid drop --- lib/migration_generator/ash_functions.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 5b91a3c7..957f8689 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -137,7 +137,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do end def drop(nil) do - "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE) ash_trim_whitespace(text[])\")" + "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" end defp ash_raise_error(prefix? \\ true) do From 3c2b8912d0099b05911117995e63e3ef4f5691a9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 7 Mar 2024 11:33:23 -0500 Subject: [PATCH 0293/1215] fix: don't reuse binding in many to many aggregate joins --- lib/aggregate.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 1582d8ca..71d69c99 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -209,6 +209,9 @@ defmodule AshPostgres.Aggregate do ^first_relationship.source_attribute_on_join_resource ) ) + |> Map.update!(:__ash_bindings__, fn bindings -> + Map.update!(bindings, :current, &(&1 + 1)) + end) AshPostgres.Join.set_join_prefix( subquery, From abcdc052025167813ebbf3dd770e683e6afef0ad Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 11 Mar 2024 11:20:50 -0400 Subject: [PATCH 0294/1215] improvement: don't select fields in exists subquery --- lib/expr.ex | 7 +++++++ mix.lock | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index 8a1dce7c..c77e9c0e 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1457,6 +1457,13 @@ defmodule AshPostgres.Expr do ], return_subquery?: true, on_subquery: fn subquery -> + subquery = Ecto.Query.exclude(subquery, :select) + + subquery = + Ecto.Query.from(row in subquery, + select: fragment("1") + ) + cond do Map.get(first_relationship, :manual) -> subquery diff --git a/mix.lock b/mix.lock index 8c9cd596..2c370dea 100644 --- a/mix.lock +++ b/mix.lock @@ -10,9 +10,9 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"}, + "elixir_make": {:hex, :elixir_make, "0.8.2", "cd4a5a75891362e9207adaac7e66223fd256ec2518ae013af7f10c9c85b50b5c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "9d9607d640c372a7291e5a56ce655aa2351897929be20bd211648fdb79e725dc"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, @@ -22,6 +22,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, @@ -29,6 +30,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"}, "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, From 2a326ea6bd657f6ff6b9464ca4881a6b49912180 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 11 Mar 2024 11:34:40 -0400 Subject: [PATCH 0295/1215] chore: fix dialyzer/mix.lock --- lib/expr.ex | 5 ++--- mix.lock | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index c77e9c0e..55766edc 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1457,12 +1457,11 @@ defmodule AshPostgres.Expr do ], return_subquery?: true, on_subquery: fn subquery -> - subquery = Ecto.Query.exclude(subquery, :select) - subquery = - Ecto.Query.from(row in subquery, + Ecto.Query.from(row in Ecto.Query.exclude(subquery, :select), select: fragment("1") ) + |> Map.put(:__ash_bindings__, subquery.__ash_bindings__) cond do Map.get(first_relationship, :manual) -> diff --git a/mix.lock b/mix.lock index 2c370dea..13f5a93e 100644 --- a/mix.lock +++ b/mix.lock @@ -22,7 +22,6 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, @@ -30,7 +29,6 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, - "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"}, "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, From ea853e5171e129884003ed4240d05006314b2317 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 11 Mar 2024 22:08:25 -0400 Subject: [PATCH 0296/1215] improvement: properly format generated migrations --- .../migration_generator.ex | 20 +++++-------------- mix.exs | 2 +- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index e34a3f90..b358fb88 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -305,7 +305,7 @@ defmodule AshPostgres.MigrationGenerator do pretty: true ) - contents = format(contents, opts) + contents = String.trim(format(migration_file, contents, opts)) create_file(snapshot_file, snapshot_contents, force: true) create_file(migration_file, contents) end @@ -869,7 +869,7 @@ defmodule AshPostgres.MigrationGenerator do """ try do - contents = format(contents, opts) + contents = String.trim(format(migration_file, contents, opts)) if opts.dry_run do Mix.shell().info(contents) @@ -980,9 +980,10 @@ defmodule AshPostgres.MigrationGenerator do defp maybe_comment(text, _), do: text - defp format(string, opts) do + defp format(path, string, opts) do if opts.format do - Code.format_string!(string, locals_without_parens: ecto_sql_locals_without_parens()) + {func, _} = Mix.Tasks.Format.formatter_for_file(path) + func.(string) else string end @@ -999,17 +1000,6 @@ defmodule AshPostgres.MigrationGenerator do reraise exception, __STACKTRACE__ end - defp ecto_sql_locals_without_parens do - path = File.cwd!() |> Path.join("deps/ecto_sql/.formatter.exs") - - if File.exists?(path) do - {opts, _} = Code.eval_file(path) - Keyword.get(opts, :locals_without_parens, []) - else - [] - end - end - defp streamline(ops, acc \\ []) defp streamline([], acc), do: Enum.reverse(acc) diff --git a/mix.exs b/mix.exs index c9a4855a..4e79e3ef 100644 --- a/mix.exs +++ b/mix.exs @@ -12,7 +12,7 @@ defmodule AshPostgres.MixProject do [ app: :ash_postgres, version: @version, - elixir: "~> 1.11", + elixir: "~> 1.13", start_permanent: Mix.env() == :prod, deps: deps(), description: @description, From e27ce5e0747b0cae28a29f1b5f7de69495829bab Mon Sep 17 00:00:00 2001 From: Jinkyou Son Date: Wed, 13 Mar 2024 09:22:34 +0900 Subject: [PATCH 0297/1215] improvement: Add nulls_distinct option to CustomIndex (#221) --- .formatter.exs | 1 + .../dsls/DSL:-AshPostgres.DataLayer.md | 3 +- lib/custom_index.ex | 14 ++++-- .../migration_generator.ex | 1 + lib/migration_generator/operation.ex | 1 + test/migration_generator_test.exs | 45 +++++++++++++++++++ 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index 682cd1ac..c57f31c5 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -25,6 +25,7 @@ spark_locals_without_parens = [ migration_ignore_attributes: 1, migration_types: 1, name: 1, + nulls_distinct: 1, on_delete: 1, on_update: 1, polymorphic?: 1, diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index c451cb35..d5d5bcfb 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -111,8 +111,9 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" | [`using`](#postgres-custom_indexes-index-using){: #postgres-custom_indexes-index-using } | `String.t` | | configures the index type. | | [`prefix`](#postgres-custom_indexes-index-prefix){: #postgres-custom_indexes-index-prefix } | `String.t` | | specify an optional prefix for the index. | | [`where`](#postgres-custom_indexes-index-where){: #postgres-custom_indexes-index-where } | `String.t` | | specify conditions for a partial index. | -| [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | | [`include`](#postgres-custom_indexes-index-include){: #postgres-custom_indexes-index-include } | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | +| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | | specify whether null values should be considered distinct for a unique index. | +| [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | | [`all_tenants?`](#postgres-custom_indexes-index-all_tenants?){: #postgres-custom_indexes-index-all_tenants? } | `boolean` | `false` | Whether or not the index should factor in the multitenancy attribute or not. | diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 3f6fbfad..054c034e 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -11,6 +11,7 @@ defmodule AshPostgres.CustomIndex do :prefix, :where, :include, + :nulls_distinct, :message, :all_tenants? ] @@ -54,15 +55,20 @@ defmodule AshPostgres.CustomIndex do type: :string, doc: "specify conditions for a partial index." ], - message: [ - type: :string, - doc: "A custom message to use for unique indexes that have been violated" - ], include: [ type: {:list, :string}, doc: "specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs." ], + nulls_distinct: [ + type: :boolean, + doc: "specify whether null values should be considered distinct for a unique index.", + default: false + ], + message: [ + type: :string, + doc: "A custom message to use for unique indexes that have been violated" + ], all_tenants?: [ type: :boolean, default: false, diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index b358fb88..071693a6 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2988,6 +2988,7 @@ defmodule AshPostgres.MigrationGenerator do end) end) |> Map.put_new(:include, []) + |> Map.put_new(:nulls_distinct, false) |> Map.put_new(:message, nil) |> Map.put_new(:all_tenants?, false) end) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 29db69f3..cee9c612 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -908,6 +908,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do option(:prefix, index.prefix), option(:where, index.where), option(:include, index.include), + option(:nulls_distinct, index.nulls_distinct), option(:prefix, schema) ]) diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index e9f571d9..c0bb44c2 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -298,6 +298,51 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "custom_indexes with `null_distinct: true`" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + + defposts do + postgres do + custom_indexes do + index([:uniq_one], nulls_distinct: true) + index([:uniq_two], nulls_distinct: false) + index([:uniq_custom_one]) + end + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string) + end + end + + defapi([Post]) + Mix.shell(Mix.Shell.Process) + + AshPostgres.MigrationGenerator.generate(Api, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + end + + test "it adds nulls_distinct option to create index migration" do + assert [custom_index_migration] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + + file = File.read!(custom_index_migration) + + assert file =~ ~S + assert file =~ ~S + assert file =~ ~S + end + end + describe "creating follow up migrations with a schema" do setup do on_exit(fn -> From 577e2b39aca07ce75cff502a932cfb962c02a856 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 18 Mar 2024 21:16:31 -0400 Subject: [PATCH 0298/1215] chore: release version v1.5.18 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76870f1e..93d8fdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.18](https://github.com/ash-project/ash_postgres/compare/v1.5.17...v1.5.18) (2024-03-19) + + + + +### Bug Fixes: + +* don't reuse binding in many to many aggregate joins + +* typo in extension generator creates invalid drop + +* merge base_filter and custom index's where correctly (#219) + +### Improvements: + +* properly format generated migrations + +* don't select fields in exists subquery + ## [v1.5.17](https://github.com/ash-project/ash_postgres/compare/v1.5.16...v1.5.17) (2024-03-06) diff --git a/mix.exs b/mix.exs index 4e79e3ef..2a507fb2 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.17" + @version "1.5.18" def project do [ From 7313d35c19cbd3b3ced967e726afecb39519a1d4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 18 Mar 2024 21:17:02 -0400 Subject: [PATCH 0299/1215] chore: docs --- documentation/dsls/DSL:-AshPostgres.DataLayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index d5d5bcfb..cd1a8471 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -112,7 +112,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" | [`prefix`](#postgres-custom_indexes-index-prefix){: #postgres-custom_indexes-index-prefix } | `String.t` | | specify an optional prefix for the index. | | [`where`](#postgres-custom_indexes-index-where){: #postgres-custom_indexes-index-where } | `String.t` | | specify conditions for a partial index. | | [`include`](#postgres-custom_indexes-index-include){: #postgres-custom_indexes-index-include } | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | -| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | | specify whether null values should be considered distinct for a unique index. | +| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | `false` | specify whether null values should be considered distinct for a unique index. | | [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | | [`all_tenants?`](#postgres-custom_indexes-index-all_tenants?){: #postgres-custom_indexes-index-all_tenants? } | `boolean` | `false` | Whether or not the index should factor in the multitenancy attribute or not. | From 2c1381d5e639f38e5f9266c14482ce0a0fd90ced Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 19 Mar 2024 00:06:15 -0400 Subject: [PATCH 0300/1215] fix: encode maps on update using fragments --- lib/data_layer.ex | 2 +- lib/expr.ex | 132 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 109 insertions(+), 25 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 31c92f29..d5e9fc7a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2706,7 +2706,7 @@ defmodule AshPostgres.DataLayer do case AshPostgres.Expr.dynamic_expr( query, expr, - query.__ash_bindings__, + Map.put(query.__ash_bindings__, :location, :update), false, type ) do diff --git a/lib/expr.ex b/lib/expr.ex index 55766edc..f4f434e6 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -146,6 +146,32 @@ defmodule AshPostgres.Expr do } end + defp do_dynamic_expr( + query, + %IsNil{left: left, right: true, embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + _type + ) do + {left_expr, acc} = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) + + {Ecto.Query.dynamic(is_nil(^left_expr)), acc} + end + + defp do_dynamic_expr( + query, + %IsNil{left: left, right: false, embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + _type + ) do + {left_expr, acc} = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) + + {Ecto.Query.dynamic(not is_nil(^left_expr)), acc} + end + defp do_dynamic_expr( query, %IsNil{left: left, right: right, embedded?: pred_embedded?}, @@ -382,21 +408,44 @@ defmodule AshPostgres.Expr do acc, type ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(SELECT COUNT(*) FROM unnest(", - expr: list, - raw: ") AS item WHERE item IS NULL)" - ] - }, - bindings, - embedded?, - acc, - type - ) + if is_list(list) do + list = + Enum.map(list, fn item -> + %Ash.Query.Operator.IsNil{left: item, right: true} + end) + + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "(SELECT COUNT(*) FROM unnest(", + expr: list, + raw: ") AS item WHERE item IS TRUE)" + ] + }, + bindings, + embedded?, + acc, + type + ) + else + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "(SELECT COUNT(*) FROM unnest(", + expr: list, + raw: ") AS item WHERE item IS NULL)" + ] + }, + bindings, + embedded?, + acc, + type + ) + end end defp do_dynamic_expr( @@ -1598,18 +1647,53 @@ defmodule AshPostgres.Expr do {value, acc} end - defp do_dynamic_expr(query, value, bindings, embedded?, acc, _type) + defp do_dynamic_expr(query, value, bindings, embedded?, acc, type) when is_map(value) and not is_struct(value) do - {value, acc} = - Enum.reduce(value, {%{}, acc}, fn {key, value}, {map, acc} -> - {value, acc} = do_dynamic_expr(query, value, bindings, embedded?, acc) - {Map.put(map, key, value), acc} - end) + if bindings[:location] == :update && + Enum.any?(value, fn {key, value} -> + Ash.Filter.TemplateHelpers.expr?(key) || Ash.Filter.TemplateHelpers.expr?(value) + end) do + elements = + value + |> Enum.flat_map(fn {key, list_item} -> + if is_atom(key) do + [{:expr, %Ash.Query.Function.Type{arguments: [key, :atom, []]}}, {:expr, list_item}] + else + [ + {:expr, %Ash.Query.Function.Type{arguments: [key, :string, []]}}, + {:expr, list_item} + ] + end + end) + |> Enum.intersperse({:raw, ","}) - if embedded? do - {Ecto.Query.dynamic([], type(^value, :map)), acc} + do_dynamic_expr( + query, + %Fragment{ + embedded?: embedded?, + arguments: + [ + raw: "jsonb_build_object(" + ] ++ elements ++ [raw: ")"] + }, + bindings, + embedded?, + acc, + type + ) else - {value, acc} + {value, acc} = + Enum.reduce(value, {%{}, acc}, fn {key, value}, {map, acc} -> + {value, acc} = do_dynamic_expr(query, value, bindings, embedded?, acc) + + {Map.put(map, key, value), acc} + end) + + if embedded? do + {Ecto.Query.dynamic([], type(^value, :map)), acc} + else + {value, acc} + end end end From 313937db57a32128cbf14f9115c887344b2a4207 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 19 Mar 2024 00:06:49 -0400 Subject: [PATCH 0301/1215] chore: release version v1.5.19 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93d8fdaf..f6f3f884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.19](https://github.com/ash-project/ash_postgres/compare/v1.5.18...v1.5.19) (2024-03-19) + + + + +### Bug Fixes: + +* encode maps on update using fragments + +### Improvements: + +* Add nulls_distinct option to CustomIndex (#221) + ## [v1.5.18](https://github.com/ash-project/ash_postgres/compare/v1.5.17...v1.5.18) (2024-03-19) diff --git a/mix.exs b/mix.exs index 2a507fb2..05a90eb1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.18" + @version "1.5.19" def project do [ From 0c3b4e542576cb14d92d8f635603427b9ce27874 Mon Sep 17 00:00:00 2001 From: Minsub Kim Date: Wed, 20 Mar 2024 21:36:44 +0900 Subject: [PATCH 0302/1215] fix: generate correct custom index name in down migration function (#222) --- lib/migration_generator/operation.ex | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index cee9c612..0aeda09c 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -912,15 +912,14 @@ defmodule AshPostgres.MigrationGenerator.Operation do option(:prefix, schema) ]) - if opts == "", - do: "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])", - else: - "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})" + if opts == "" do + "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])" + else + "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})" + end end def down(%{schema: schema, index: index, table: table, multitenancy: multitenancy}) do - index_name = AshPostgres.CustomIndex.name(table, index) - keys = if !index.all_tenants? and multitenancy.strategy == :attribute do [multitenancy.attribute | index.fields] @@ -928,7 +927,17 @@ defmodule AshPostgres.MigrationGenerator.Operation do index.fields end - "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})" + opts = + join([ + option(:name, index.name), + option(:prefix, schema) + ]) + + if opts == "" do + "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])" + else + "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})" + end end end From b60f680ab354b68ebce7e82f8ab20e759324c405 Mon Sep 17 00:00:00 2001 From: Jinkyou Son Date: Thu, 21 Mar 2024 01:01:53 +0900 Subject: [PATCH 0303/1215] Fix: undo default of nulls_distinct option to true (#223) --- lib/custom_index.ex | 2 +- lib/migration_generator/migration_generator.ex | 2 +- lib/migration_generator/operation.ex | 6 ++++++ test/migration_generator_test.exs | 6 +++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 054c034e..4b77cae3 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -63,7 +63,7 @@ defmodule AshPostgres.CustomIndex do nulls_distinct: [ type: :boolean, doc: "specify whether null values should be considered distinct for a unique index.", - default: false + default: true ], message: [ type: :string, diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 071693a6..221c58ae 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2988,7 +2988,7 @@ defmodule AshPostgres.MigrationGenerator do end) end) |> Map.put_new(:include, []) - |> Map.put_new(:nulls_distinct, false) + |> Map.put_new(:nulls_distinct, true) |> Map.put_new(:message, nil) |> Map.put_new(:all_tenants?, false) end) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 0aeda09c..97092c40 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -30,6 +30,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do # sobelow_skip ["DOS.StringToAtom"] def as_atom(value), do: Macro.inspect_atom(:remote_call, String.to_atom(value)) + def option(:nulls_distinct = key, value) do + if !value do + "#{as_atom(key)}: #{inspect(value)}" + end + end + def option(key, value) do if value do "#{as_atom(key)}: #{inspect(value)}" diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index c0bb44c2..eb0810c1 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -298,7 +298,7 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - describe "custom_indexes with `null_distinct: true`" do + describe "custom_indexes with `null_distinct: false`" do setup do on_exit(fn -> File.rm_rf!("test_snapshots_path") @@ -337,8 +337,8 @@ defmodule AshPostgres.MigrationGeneratorTest do file = File.read!(custom_index_migration) - assert file =~ ~S - assert file =~ ~S + assert file =~ ~S + assert file =~ ~S assert file =~ ~S end end From f62e0ff8e2cb92865428b22d127c8e39a1868326 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Mar 2024 12:04:31 -0400 Subject: [PATCH 0304/1215] chore: release version v1.5.20 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f3f884..f788a15d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.20](https://github.com/ash-project/ash_postgres/compare/v1.5.19...v1.5.20) (2024-03-20) + + + + +### Bug Fixes: + +* undo default of nulls_distinct option to true (#223) + +* generate correct custom index name in down migration function (#222) + ## [v1.5.19](https://github.com/ash-project/ash_postgres/compare/v1.5.18...v1.5.19) (2024-03-19) diff --git a/mix.exs b/mix.exs index 05a90eb1..7718e680 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.19" + @version "1.5.20" def project do [ From adac811c797d51e158faa2609488e21433d444f0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Mar 2024 16:18:27 -0400 Subject: [PATCH 0305/1215] fix: properly format migrations fix: ensure exists aggregates have filters included --- .tool-versions | 2 +- documentation/dsls/DSL:-AshPostgres.DataLayer.md | 2 +- lib/expr.ex | 16 ++++++++++++++-- lib/migration_generator/migration_generator.ex | 4 ++-- mix.exs | 2 +- mix.lock | 6 ++++-- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.tool-versions b/.tool-versions index b80e3611..9a50e189 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 26.2.2 -elixir 1.16.1 +elixir 1.16.2 diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index cd1a8471..b70c3365 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -112,7 +112,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" | [`prefix`](#postgres-custom_indexes-index-prefix){: #postgres-custom_indexes-index-prefix } | `String.t` | | specify an optional prefix for the index. | | [`where`](#postgres-custom_indexes-index-where){: #postgres-custom_indexes-index-where } | `String.t` | | specify conditions for a partial index. | | [`include`](#postgres-custom_indexes-index-include){: #postgres-custom_indexes-index-include } | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | -| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | `false` | specify whether null values should be considered distinct for a unique index. | +| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | `true` | specify whether null values should be considered distinct for a unique index. | | [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | | [`all_tenants?`](#postgres-custom_indexes-index-all_tenants?){: #postgres-custom_indexes-index-all_tenants? } | `boolean` | `false` | Whether or not the index should factor in the multitenancy attribute or not. | diff --git a/lib/expr.ex b/lib/expr.ex index f4f434e6..5da45abc 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1096,7 +1096,8 @@ defmodule AshPostgres.Expr do %Ref{ attribute: %Ash.Query.Aggregate{ kind: :exists, - relationship_path: agg_relationship_path + relationship_path: agg_relationship_path, + query: agg_query }, relationship_path: ref_relationship_path }, @@ -1105,9 +1106,20 @@ defmodule AshPostgres.Expr do acc, type ) do + filter = + if is_nil(agg_query.filter) do + true + else + agg_query.filter + end + do_dynamic_expr( query, - %Ash.Query.Exists{path: agg_relationship_path, expr: true, at_path: ref_relationship_path}, + %Ash.Query.Exists{ + path: agg_relationship_path, + expr: filter, + at_path: ref_relationship_path + }, bindings, embedded?, acc, diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 221c58ae..cbe2c7d2 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -305,7 +305,7 @@ defmodule AshPostgres.MigrationGenerator do pretty: true ) - contents = String.trim(format(migration_file, contents, opts)) + contents = format(migration_file, contents, opts) create_file(snapshot_file, snapshot_contents, force: true) create_file(migration_file, contents) end @@ -869,7 +869,7 @@ defmodule AshPostgres.MigrationGenerator do """ try do - contents = String.trim(format(migration_file, contents, opts)) + contents = format(migration_file, contents, opts) if opts.dry_run do Mix.shell().info(contents) diff --git a/mix.exs b/mix.exs index 7718e680..58906f75 100644 --- a/mix.exs +++ b/mix.exs @@ -157,7 +157,7 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.19 and >= 2.19.6")}, + {:ash, ash_version("~> 2.19 and >= 2.20.3")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 13f5a93e..3f15e7d4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.19.10", "01a8d7f7728c95fdaefb2ca10fb7e43813ae049f45b9640a30e53c064ad2650b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.50 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c862406eb2fb8801e77897060722a690ae1e069fd82fc52b6874e2a8d0b164a0"}, + "ash": {:hex, :ash, "2.20.3", "2ded1295fd20e2a45b01c678fe93c51397384ec5e5e4babc80f1ae9ce896ca82", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.6", [hex: :reactor, repo: "hexpm", optional: false]}, {:spark, ">= 1.1.55 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b53374c6c70da21bb8d53fefb88e1b0dc7a6fd8cf48ecaff4d6e57d2e69afbca"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -22,6 +22,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, @@ -29,8 +30,9 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"}, + "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From a350600f9b5b6a0ebcbf3cb4f27cf2318d493e8e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Mar 2024 16:21:13 -0400 Subject: [PATCH 0306/1215] chore: release version v1.5.21 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f788a15d..364ef670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.21](https://github.com/ash-project/ash_postgres/compare/v1.5.20...v1.5.21) (2024-03-20) + + + + +### Bug Fixes: + +* properly format migrations + +* ensure exists aggregates have filters included + ## [v1.5.20](https://github.com/ash-project/ash_postgres/compare/v1.5.19...v1.5.20) (2024-03-20) diff --git a/mix.exs b/mix.exs index 58906f75..4d5410f9 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.20" + @version "1.5.21" def project do [ From a0e26939981861746f1d78a25ba7da913d70b19c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Mar 2024 18:37:38 -0400 Subject: [PATCH 0307/1215] fix: don't fail on aggregate query generation fixes #225 --- lib/aggregate.ex | 2 +- test/aggregate_test.exs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 71d69c99..a608aa6f 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1198,7 +1198,7 @@ defmodule AshPostgres.Aggregate do has_sort? = has_sort?(aggregate.query) {sorted, query} = - if has_sort? || first_relationship.sort not in [nil, []] do + if has_sort? || (first_relationship && first_relationship.sort not in [nil, []]) do {sort, binding} = if has_sort? do {aggregate.query.sort, binding} diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index d633bc42..ff6a1837 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1164,6 +1164,12 @@ defmodule AshPostgres.AggregateTest do |> Api.count!() end + test "a list with a filter that references a to many relationship can be aggregated at the query level" do + Post + |> Ash.Query.filter(comments.likes > 10) + |> Api.list!(:title) + end + test "a count with a limit and a filter can be aggregated at the query level" do Post |> Ash.Changeset.new(%{title: "foo"}) From c2ec4f8127b3741ed81b412dff8ff614108c410e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 20 Mar 2024 18:38:23 -0400 Subject: [PATCH 0308/1215] chore: release version v1.5.22 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 364ef670..8ea052f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v1.5.22](https://github.com/ash-project/ash_postgres/compare/v1.5.21...v1.5.22) (2024-03-20) + + + + +### Bug Fixes: + +* don't fail on aggregate query generation + ## [v1.5.21](https://github.com/ash-project/ash_postgres/compare/v1.5.20...v1.5.21) (2024-03-20) diff --git a/mix.exs b/mix.exs index 4d5410f9..19c0d256 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.21" + @version "1.5.22" def project do [ From 5a513a82c232761a44870129be40ab3d1b147ebd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Mar 2024 02:13:07 -0400 Subject: [PATCH 0309/1215] fix: handle fully fleshed out aggregate fields --- .../dsls/DSL:-AshPostgres.DataLayer.md | 1 - lib/aggregate.ex | 155 +++++++++++++++--- lib/expr.ex | 71 +++++++- test/aggregate_test.exs | 23 +++ test/support/resources/author.ex | 4 + 5 files changed, 220 insertions(+), 34 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index b70c3365..f3c18042 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -3,7 +3,6 @@ This file was generated by Spark. Do not edit it by hand. --> # DSL: AshPostgres.DataLayer -A postgres data layer that leverages Ecto's postgres capabilities. ## postgres diff --git a/lib/aggregate.ex b/lib/aggregate.ex index a608aa6f..581ee86e 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -650,8 +650,26 @@ defmodule AshPostgres.Aggregate do related = Ash.Resource.Info.related(first_relationship.destination, relationship_path) + field = + case aggregate.field do + field when is_atom(field) -> + Ash.Resource.Info.field(related, field) + + field -> + field + end + agg_query = - case Ash.Resource.Info.field(related, aggregate.field) do + case field do + %Ash.Query.Aggregate{} = aggregate -> + {:ok, agg_query} = + add_aggregates(agg_query, [aggregate], related, false, 0, { + first_relationship.destination, + [first_relationship.name] + }) + + agg_query + %Ash.Resource.Aggregate{} = aggregate -> {:ok, agg_query} = add_aggregates(agg_query, [aggregate], related, false, 0, { @@ -694,6 +712,36 @@ defmodule AshPostgres.Aggregate do agg_query + %Ash.Query.Calculation{ + module: module, + opts: opts + } = calc -> + expression = module.expression(opts, aggregate.context) + + expression = + Ash.Filter.build_filter_from_template( + expression, + aggregate.context[:actor], + aggregate.context, + aggregate.context + ) + + {:ok, expression} = + Ash.Filter.hydrate_refs(expression, %{ + resource: related, + public?: false + }) + + {:ok, agg_query} = + AshPostgres.DataLayer.add_calculations( + agg_query, + [{calc, expression}], + agg_query.__ash_bindings__.resource, + false + ) + + agg_query + _ -> agg_query end @@ -910,6 +958,42 @@ defmodule AshPostgres.Aggregate do end @doc false + def optimizable_first_aggregate?( + resource, + %{ + kind: :first, + relationship_path: relationship_path, + join_filters: join_filters, + field: %Ash.Query.Calculation{} = field + } + ) do + ref = + %Ash.Query.Ref{ + attribute: field, + relationship_path: relationship_path, + resource: resource + } + + with true <- join_filters == %{}, + [] <- Ash.Filter.used_aggregates(ref, :all), + [] <- Ash.Filter.relationship_paths(ref) do + true + else + _ -> + false + end + end + + def optimizable_first_aggregate?( + _resource, + %{ + kind: :first, + field: %Ash.Query.Aggregate{} + } + ) do + false + end + def optimizable_first_aggregate?( resource, %{ @@ -945,6 +1029,9 @@ defmodule AshPostgres.Aggregate do false end + nil -> + false + _ -> name in AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) || (join_filters in [nil, %{}, []] && @@ -957,12 +1044,24 @@ defmodule AshPostgres.Aggregate do defp array_type?(resource, aggregate) do related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - case Ash.Resource.Info.field(related, aggregate.field).type do - {:array, _} -> + case aggregate.field do + nil -> false - _ -> + %{type: {:array, _}} -> true + + type when is_atom(type) -> + case Ash.Resource.Info.field(related, aggregate.field).type do + {:array, _} -> + false + + _ -> + true + end + + _ -> + false end end @@ -1482,32 +1581,36 @@ defmodule AshPostgres.Aggregate do @doc false def aggregate_field(aggregate, resource) do - case Ash.Resource.Info.field( - resource, - aggregate.field || List.first(Ash.Resource.Info.primary_key(resource)) - ) do - %Ash.Resource.Calculation{calculation: {module, opts}} = calculation -> - calc_type = - AshPostgres.Types.parameterized_type( - calculation.type, - Map.get(calculation, :constraints, []) - ) + if is_atom(aggregate.field) do + case Ash.Resource.Info.field( + resource, + aggregate.field || List.first(Ash.Resource.Info.primary_key(resource)) + ) do + %Ash.Resource.Calculation{calculation: {module, opts}} = calculation -> + calc_type = + AshPostgres.Types.parameterized_type( + calculation.type, + Map.get(calculation, :constraints, []) + ) - AshPostgres.Expr.validate_type!(resource, calc_type, "#{inspect(calculation.name)}") + AshPostgres.Expr.validate_type!(resource, calc_type, "#{inspect(calculation.name)}") - {:ok, query_calc} = - Ash.Query.Calculation.new( - calculation.name, - module, - opts, - calculation.type, - Map.get(aggregate, :context, %{}) - ) + {:ok, query_calc} = + Ash.Query.Calculation.new( + calculation.name, + module, + opts, + calculation.type, + Map.get(aggregate, :context, %{}) + ) - query_calc + query_calc - other -> - other + other -> + other + end + else + aggregate.field end end end diff --git a/lib/expr.ex b/lib/expr.ex index 5da45abc..8cd6b87a 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -308,9 +308,16 @@ defmodule AshPostgres.Expr do ) when is_list(right) do attribute = - if aggregate.field do - related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - Ash.Resource.Info.attribute(related, aggregate.field) + case aggregate.field do + nil -> + nil + + %{} = field -> + field + + field -> + related = Ash.Resource.Info.related(resource, aggregate.relationship_path) + Ash.Resource.Info.attribute(related, field) end attribute_type = @@ -1097,7 +1104,8 @@ defmodule AshPostgres.Expr do attribute: %Ash.Query.Aggregate{ kind: :exists, relationship_path: agg_relationship_path, - query: agg_query + query: agg_query, + join_filters: join_filters }, relationship_path: ref_relationship_path }, @@ -1119,7 +1127,8 @@ defmodule AshPostgres.Expr do path: agg_relationship_path, expr: filter, at_path: ref_relationship_path - }, + } + |> Map.put(:__join_filters__, join_filters), bindings, embedded?, acc, @@ -1186,6 +1195,11 @@ defmodule AshPostgres.Expr do {value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc) + case aggregate.field do + %{name: name} -> name + field -> field + end + {ref_binding, aggregate.field, value, acc} else ref_binding = ref_binding(ref, bindings) @@ -1500,7 +1514,7 @@ defmodule AshPostgres.Expr do defp do_dynamic_expr( query, - %Exists{at_path: at_path, path: [first | rest], expr: expr}, + %Exists{at_path: at_path, path: [first | rest], expr: expr} = exists, bindings, _embedded?, acc, @@ -1509,9 +1523,52 @@ defmodule AshPostgres.Expr do resource = Ash.Resource.Info.related(bindings.resource, at_path) first_relationship = Ash.Resource.Info.relationship(resource, first) + filter = Ash.Filter.move_to_relationship_path(expr, rest) + + filter = + exists + |> Map.get(:__join_filters__, %{}) + |> Map.fetch([first_relationship.name]) + |> case do + {:ok, join_filter} -> + Ash.Query.BooleanExpression.optimized_new( + :and, + filter, + Ash.Filter.move_to_relationship_path( + join_filter, + rest ++ [first_relationship.name] + ) + ) + + :error -> + filter + end + + filter = + exists + |> Map.get(:__join_filters__, %{}) + |> Map.delete([first_relationship.name]) + |> Enum.reduce(filter, fn {path, path_filter}, filter -> + path = Enum.drop(path, 1) + parent_path = :lists.droplast(path) + + Ash.Query.BooleanExpression.optimized_new( + :and, + filter, + Ash.Filter.move_to_relationship_path(path_filter, path) + ) + |> Ash.Filter.map(fn + %Ash.Query.Parent{expr: expr} -> + {:halt, Ash.Filter.move_to_relationship_path(expr, parent_path)} + + other -> + other + end) + end) + {:ok, subquery} = AshPostgres.Join.related_subquery(first_relationship, query, - filter: Ash.Filter.move_to_relationship_path(expr, rest), + filter: filter, parent_resources: [ query.__ash_bindings__.resource | query.__ash_bindings__[:parent_resources] || [] diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index ff6a1837..162c3e6f 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -95,6 +95,29 @@ defmodule AshPostgres.AggregateTest do |> Ash.Query.load(:count_of_posts_with_better_comment) |> Api.read!() end + + test "it properly applies join criteria to exists queries in filters" do + author = + Author + |> Ash.Changeset.new(%{}) + |> Api.create!() + + non_matching_post = + Post + |> Ash.Changeset.new(%{title: "non_match", score: 100}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Api.create!() + + Comment + |> Ash.Changeset.new(%{title: "non_match", likes: 0}) + |> Ash.Changeset.manage_relationship(:post, non_matching_post, type: :append_and_remove) + |> Api.create!() + + assert [] = + Author + |> Ash.Query.filter(has_post_with_better_comment) + |> Api.read!() + end end describe "count" do diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 98d35691..51d32184 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -129,6 +129,10 @@ defmodule AshPostgres.Test.Author do join_filter([:posts, :comments], expr(parent(score) < likes)) end + exists :has_post_with_better_comment, [:posts, :comments] do + join_filter([:posts, :comments], expr(parent(score) < likes)) + end + count(:num_of_authors_with_same_first_name, :authors_with_same_first_name) end end From ec75b41dbe976b34a6003b405f07187f74e2cc47 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 22 Mar 2024 14:20:38 -0400 Subject: [PATCH 0310/1215] improvement: properly show unsupported error expression --- documentation/dsls/DSL:-AshPostgres.DataLayer.md | 1 + lib/data_layer.ex | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index f3c18042..b70c3365 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -3,6 +3,7 @@ This file was generated by Spark. Do not edit it by hand. --> # DSL: AshPostgres.DataLayer +A postgres data layer that leverages Ecto's postgres capabilities. ## postgres diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d5e9fc7a..995bc231 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -515,6 +515,12 @@ defmodule AshPostgres.DataLayer do def can?(_, :timeout), do: true def can?(_, :expr_error), do: true + + def can?(resource, {:filter_expr, %Ash.Query.Function.Error{}}) do + "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :read).installed_extensions() && + "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :mutate).installed_extensions() + end + def can?(_, {:filter_expr, _}), do: true def can?(_, :nested_expressions), do: true def can?(_, {:query_aggregate, _}), do: true From 37cc01957d34381bfaee0438125a68e46ff1b4ae Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 16:52:28 -0400 Subject: [PATCH 0311/1215] improvement!: 3.0 (#227) * WIP * chore: fix mix.lock merge issues * improvement: upgrade to 3.0 * chore: remove `repo.to_tenant` * chore: continue removal of unnecessary helper * chore: use `Ash.ToTenant` --- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- .github/workflows/elixir.yml | 2 +- CHANGELOG.md | 3093 +++++++---------- README.md | 2 +- benchmarks/bulk_create.exs | 16 +- config/config.exs | 12 +- .../how_to/join-manual-relationships.md | 2 +- documentation/topics/migrations_and_tasks.md | 14 +- documentation/topics/polymorphic_resources.md | 10 +- .../tutorials/get-started-with-postgres.md | 4 +- lib/aggregate.ex | 110 +- lib/calculation.ex | 9 +- lib/data_layer.ex | 176 +- lib/data_layer/info.ex | 15 + lib/expr.ex | 49 +- lib/functions/fragment.ex | 72 - .../migration_generator.ex | 10 +- lib/mix/helpers.ex | 46 +- lib/mix/tasks/ash_postgres.create.ex | 10 +- lib/mix/tasks/ash_postgres.drop.ex | 10 +- .../tasks/ash_postgres.generate_migrations.ex | 8 +- lib/mix/tasks/ash_postgres.migrate.ex | 12 +- lib/mix/tasks/ash_postgres.rollback.ex | 8 +- lib/repo.ex | 9 +- .../ensure_table_or_polymorphic.ex | 16 +- ...te_multitenancy_and_non_full_match_type.ex | 16 +- ...event_multidimensional_array_aggregates.ex | 14 +- .../validate_references.ex | 14 +- lib/verifiers/verify_postgres_version.ex | 37 + mix.exs | 8 +- mix.lock | 12 +- test/aggregate_test.exs | 646 ++-- test/ash_postgres_test.exs | 12 +- test/atomics_test.exs | 32 +- test/bulk_create_test.exs | 34 +- test/bulk_destroy_test.exs | 30 +- test/bulk_update_test.exs | 60 +- test/calculation_test.exs | 334 +- test/complex_calculations_test.exs | 108 +- test/composite_type_test.exs | 14 +- test/constraint_test.exs | 6 +- test/custom_index_test.exs | 14 +- test/distinct_test.exs | 58 +- test/embeddable_resource_test.exs | 20 +- test/enum_test.exs | 6 +- test/error_expr_test.exs | 20 +- test/filter_field_policy_test.exs | 12 +- test/filter_test.exs | 502 ++- test/load_test.exs | 162 +- test/lock_test.exs | 8 +- test/manual_relationships_test.exs | 242 +- test/manual_update_test.exs | 10 +- test/migration_generator_test.exs | 433 ++- test/multitenancy_test.exs | 84 +- test/polymorphism_test.exs | 10 +- test/primary_key_test.exs | 17 +- test/references_test.exs | 25 +- test/rel_with_parent_filter_test.exs | 14 +- test/schema_test.exs | 24 +- test/select_test.exs | 8 +- test/sort_test.exs | 102 +- test/support/api.ex | 8 - test/support/complex_calculations/api.ex | 8 - test/support/complex_calculations/domain.ex | 17 + test/support/complex_calculations/registry.ex | 13 - .../resources/certification.ex | 9 +- .../complex_calculations/resources/channel.ex | 18 +- .../resources/channel_member.ex | 11 +- .../resources/dm_channel.ex | 10 +- .../resources/documentation.ex | 17 +- .../complex_calculations/resources/skill.ex | 14 +- test/support/concat.ex | 10 +- test/support/domain.ex | 27 + test/support/multitenancy/api.ex | 8 - test/support/multitenancy/domain.ex | 10 + test/support/multitenancy/registry.ex | 10 - test/support/multitenancy/resources/org.ex | 16 +- test/support/multitenancy/resources/post.ex | 17 +- test/support/multitenancy/resources/user.ex | 11 +- test/support/registry.ex | 23 - .../comments_containing_title.ex | 2 +- test/support/resources/account.ex | 12 +- test/support/resources/author.ex | 23 +- test/support/resources/bio.ex | 9 +- test/support/resources/comment.ex | 21 +- test/support/resources/entity.ex | 7 +- test/support/resources/integer_post.ex | 5 +- test/support/resources/manager.ex | 12 +- test/support/resources/organization.ex | 19 +- test/support/resources/post.ex | 88 +- test/support/resources/post_follower.ex | 5 + test/support/resources/post_link.ex | 6 + test/support/resources/post_views.ex | 9 +- test/support/resources/profile.ex | 9 +- test/support/resources/rating.ex | 7 +- test/support/resources/record.ex | 8 +- test/support/resources/subquery/access.ex | 13 +- test/support/resources/subquery/child.ex | 8 +- .../{child_api.ex => child_domain.ex} | 8 +- test/support/resources/subquery/parent.ex | 18 +- .../{parent_api.ex => parent_domain.ex} | 8 +- test/support/resources/subquery/through.ex | 13 +- test/support/resources/temp_entity.ex | 7 +- test/support/resources/user.ex | 15 +- test/support/test_no_sandbox_repo.ex | 6 +- test/support/test_repo.ex | 6 +- test/support/types/money.ex | 2 + test/transaction_test.exs | 16 +- test/type_test.exs | 18 +- test/unique_identity_test.exs | 27 +- test/upsert_test.exs | 10 +- test_snapshot_path/extensions.json | 10 - 112 files changed, 3580 insertions(+), 3974 deletions(-) delete mode 100644 lib/functions/fragment.ex rename lib/{transformers => verifiers}/ensure_table_or_polymorphic.ex (51%) rename lib/{transformers => verifiers}/prevent_attribute_multitenancy_and_non_full_match_type.ex (81%) rename lib/{transformers => verifiers}/prevent_multidimensional_array_aggregates.ex (80%) rename lib/{transformers => verifiers}/validate_references.ex (65%) create mode 100644 lib/verifiers/verify_postgres_version.ex delete mode 100644 test/support/api.ex delete mode 100644 test/support/complex_calculations/api.ex create mode 100644 test/support/complex_calculations/domain.ex delete mode 100644 test/support/complex_calculations/registry.ex create mode 100644 test/support/domain.ex delete mode 100644 test/support/multitenancy/api.ex create mode 100644 test/support/multitenancy/domain.ex delete mode 100644 test/support/multitenancy/registry.ex delete mode 100644 test/support/registry.ex rename test/support/resources/subquery/{child_api.ex => child_domain.ex} (57%) rename test/support/resources/subquery/{parent_api.ex => parent_domain.ex} (57%) delete mode 100644 test_snapshot_path/extensions.json diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index f347dcbc..a6442e0d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Proposal about: Suggest an idea for this project -title: '' +title: "" labels: enhancement, needs review -assignees: '' - +assignees: "" --- **Is your feature request related to a problem? Please describe.** @@ -29,7 +28,7 @@ For example Or ```elixir - Api.read(:resource, bar: 10) # <- Adding `bar` here would cause + Ash.read(Resource, bar: 10) # <- Adding `bar` here would cause ``` **Additional context** diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 29352c70..4590fca9 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - postgres-version: ["10", "12", "14", "16"] + postgres-version: ["14", "15", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea052f5..adea6ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,4268 +7,3543 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v1.5.22](https://github.com/ash-project/ash_postgres/compare/v1.5.21...v1.5.22) (2024-03-20) - - - ### Bug Fixes: -* don't fail on aggregate query generation +- don't fail on aggregate query generation ## [v1.5.21](https://github.com/ash-project/ash_postgres/compare/v1.5.20...v1.5.21) (2024-03-20) - - - ### Bug Fixes: -* properly format migrations +- properly format migrations -* ensure exists aggregates have filters included +- ensure exists aggregates have filters included ## [v1.5.20](https://github.com/ash-project/ash_postgres/compare/v1.5.19...v1.5.20) (2024-03-20) - - - ### Bug Fixes: -* undo default of nulls_distinct option to true (#223) +- undo default of nulls_distinct option to true (#223) -* generate correct custom index name in down migration function (#222) +- generate correct custom index name in down migration function (#222) ## [v1.5.19](https://github.com/ash-project/ash_postgres/compare/v1.5.18...v1.5.19) (2024-03-19) - - - ### Bug Fixes: -* encode maps on update using fragments +- encode maps on update using fragments ### Improvements: -* Add nulls_distinct option to CustomIndex (#221) +- Add nulls_distinct option to CustomIndex (#221) ## [v1.5.18](https://github.com/ash-project/ash_postgres/compare/v1.5.17...v1.5.18) (2024-03-19) - - - ### Bug Fixes: -* don't reuse binding in many to many aggregate joins +- don't reuse binding in many to many aggregate joins -* typo in extension generator creates invalid drop +- typo in extension generator creates invalid drop -* merge base_filter and custom index's where correctly (#219) +- merge base_filter and custom index's where correctly (#219) ### Improvements: -* properly format generated migrations +- properly format generated migrations -* don't select fields in exists subquery +- don't select fields in exists subquery ## [v1.5.17](https://github.com/ash-project/ash_postgres/compare/v1.5.16...v1.5.17) (2024-03-06) - - - ### Bug Fixes: -* prevent ecto/pg from getting confused about the type of maps +- prevent ecto/pg from getting confused about the type of maps ## [v1.5.16](https://github.com/ash-project/ash_postgres/compare/v1.5.15...v1.5.16) (2024-03-05) - - - ### Bug Fixes: -* always exclude `:order_by` on bulk updateable query +- always exclude `:order_by` on bulk updateable query -* don't apply join relationship sort for lateral join +- don't apply join relationship sort for lateral join ## [v1.5.15](https://github.com/ash-project/ash_postgres/compare/v1.5.14...v1.5.15) (2024-03-01) - - - ### Improvements: -* don't double cast to the same type +- don't double cast to the same type -* detect more types +- detect more types ## [v1.5.14](https://github.com/ash-project/ash_postgres/compare/v1.5.13...v1.5.14) (2024-03-01) - - - ### Improvements: -* no need for subquery for simple table aliases +- no need for subquery for simple table aliases ## [v1.5.13](https://github.com/ash-project/ash_postgres/compare/v1.5.12...v1.5.13) (2024-02-29) - - - ### Bug Fixes: -* properly handle multiple sorts in aggregate +- properly handle multiple sorts in aggregate ## [v1.5.12](https://github.com/ash-project/ash_postgres/compare/v1.5.11...v1.5.12) (2024-02-29) - - - ### Bug Fixes: -* ensure that `from_many?` joins are properly limited +- ensure that `from_many?` joins are properly limited -* ensure that lateral joins are properly filtered +- ensure that lateral joins are properly filtered ## [v1.5.11](https://github.com/ash-project/ash_postgres/compare/v1.5.10...v1.5.11) (2024-02-29) - - - ### Bug Fixes: -* simplify(and fix) exists subquery generation +- simplify(and fix) exists subquery generation -* properly leverage subqueries throughout relationship joining +- properly leverage subqueries throughout relationship joining -* migration generator extensions in multiple repos (#214) +- migration generator extensions in multiple repos (#214) -* Migration generator for extensions in multiple repos +- Migration generator for extensions in multiple repos ### Improvements: -* optimize more cases for simple join aggregates +- optimize more cases for simple join aggregates ## [v1.5.10](https://github.com/ash-project/ash_postgres/compare/v1.5.9...v1.5.10) (2024-02-26) - - - ### Bug Fixes: -* fix error when encoding vectors +- fix error when encoding vectors -* ensure select is applied (or not) properly in bulk update/destroys +- ensure select is applied (or not) properly in bulk update/destroys ## [v1.5.9](https://github.com/ash-project/ash_postgres/compare/v1.5.8...v1.5.9) (2024-02-25) - - - ### Bug Fixes: -* handle more subquery filter cases for aggregates +- handle more subquery filter cases for aggregates -* only apply filters inside aggregate subquery +- only apply filters inside aggregate subquery ### Improvements: -* add test for aggregates +- add test for aggregates ## [v1.5.8](https://github.com/ash-project/ash_postgres/compare/v1.5.7...v1.5.8) (2024-02-24) - - - ### Bug Fixes: -* properly handle complex types in lists +- properly handle complex types in lists ## [v1.5.7](https://github.com/ash-project/ash_postgres/compare/v1.5.6...v1.5.7) (2024-02-22) - - - ### Bug Fixes: -* properly apply lateral join conditions to left lateral joins +- properly apply lateral join conditions to left lateral joins ## [v1.5.6](https://github.com/ash-project/ash_postgres/compare/v1.5.5...v1.5.6) (2024-02-21) - - - ### Bug Fixes: -* ensure select is properly set on delete_all +- ensure select is properly set on delete_all ### Improvements: -* optimize aggregate query filtering +- optimize aggregate query filtering ## [v1.5.5](https://github.com/ash-project/ash_postgres/compare/v1.5.4...v1.5.5) (2024-02-21) - - - ### Bug Fixes: -* ensure proper return value for single aggregate runs +- ensure proper return value for single aggregate runs ## [v1.5.4](https://github.com/ash-project/ash_postgres/compare/v1.5.3...v1.5.4) (2024-02-21) - - - ### Bug Fixes: -* don't sort a query that will be used with `delete_all` +- don't sort a query that will be used with `delete_all` -* ensure that `exists?` aggregates use `repo.exists?` +- ensure that `exists?` aggregates use `repo.exists?` -* properly handle to_many joins in aggregates +- properly handle to_many joins in aggregates -* honor aggregate query filters +- honor aggregate query filters -* use proper tables in joins originating from polymorphic resource (#211) +- use proper tables in joins originating from polymorphic resource (#211) -* properly transfer table names to non-inner wrapper queries (#210) +- properly transfer table names to non-inner wrapper queries (#210) ## [v1.5.3](https://github.com/ash-project/ash_postgres/compare/v1.5.2...v1.5.3) (2024-02-19) - - - ### Bug Fixes: -* handle non-inner joins in delete_all +- handle non-inner joins in delete_all -* handle non-inner joins in update +- handle non-inner joins in update ## [v1.5.2](https://github.com/ash-project/ash_postgres/compare/v1.5.1...v1.5.2) (2024-02-19) - - - ### Bug Fixes: -* don't update_all or delete_all with `order_by` +- don't update_all or delete_all with `order_by` -* handle updating from queries w/ non-inner initial joins +- handle updating from queries w/ non-inner initial joins ## [v1.5.1](https://github.com/ash-project/ash_postgres/compare/v1.5.0...v1.5.1) (2024-02-19) - - - ### Bug Fixes: -* joining to `from_many?: true` relationships not honoring limit +- joining to `from_many?: true` relationships not honoring limit ## [v1.5.0](https://github.com/ash-project/ash_postgres/compare/v1.4.0...v1.5.0) (2024-02-16) - - - ### Features: -* Make MigrationGenerator accept atoms (#201) +- Make MigrationGenerator accept atoms (#201) ### Bug Fixes: -* allow subquerying a `through` while aggregating a many to many +- allow subquerying a `through` while aggregating a many to many -* don't subquery if we need to reference `parent_as` +- don't subquery if we need to reference `parent_as` -* avoid double wrapping in subqueries +- avoid double wrapping in subqueries -* properly set 0 binding on joined subquery creation +- properly set 0 binding on joined subquery creation -* properly alter renaming attributes in migration generator +- properly alter renaming attributes in migration generator -* handle original data not available in destroy_query +- handle original data not available in destroy_query -* use primary key of source as join key +- use primary key of source as join key -* use pkey if error fields is empty +- use pkey if error fields is empty -* forgot to bind keys to a variable 🤦🏻 +- forgot to bind keys to a variable 🤦🏻 -* ensure identity keys is never missing +- ensure identity keys is never missing -* properly build subqueries when required for relationship queries +- properly build subqueries when required for relationship queries -* only migrate/rollback one repo at a time +- only migrate/rollback one repo at a time -* proper return types for updates from queries +- proper return types for updates from queries -* allow atomics to return `nil` +- allow atomics to return `nil` -* Correct the matching used in building a distinct expression (#196) +- Correct the matching used in building a distinct expression (#196) -* only rollback to savepoint on specific errors +- only rollback to savepoint on specific errors -* keep fields of `custom_index` in format that they were provided (#195) +- keep fields of `custom_index` in format that they were provided (#195) -* remap selected fields, don't subquery in aggregate joins +- remap selected fields, don't subquery in aggregate joins -* include explicit schema in snapshot folder name +- include explicit schema in snapshot folder name -* Support all_tenants? in custom index (#194) +- Support all_tenants? in custom index (#194) ### Improvements: -* update to latest ash +- update to latest ash -* mark (i)like functions as predicates (#205) +- mark (i)like functions as predicates (#205) -* detect bigserial when altering attributes +- detect bigserial when altering attributes -* Include modules in installed_extensions return type (#202) +- Include modules in installed_extensions return type (#202) -* don't drop primary key in case of removal +- don't drop primary key in case of removal -* handle if select is present on query +- handle if select is present on query -* support `Ash.Changeset.OriginalDataNotAvailable` +- support `Ash.Changeset.OriginalDataNotAvailable` -* support `count_nils` expression +- support `count_nils` expression -* `error_fields` for `custom_index` +- `error_fields` for `custom_index` -* support latest ash changes +- support latest ash changes ## [v1.4.0](https://github.com/ash-project/ash_postgres/compare/v1.3.68...v1.4.0) (2024-01-12) - - - ### Features: -* Add unit test to check lateral joins +- Add unit test to check lateral joins ### Bug Fixes: -* unset sort/distinct on related queries +- unset sort/distinct on related queries -* subquery relationships that have filters +- subquery relationships that have filters -* don't overwrite manually set schema on lateral join query +- don't overwrite manually set schema on lateral join query -* properly configure `polymorphic_name` option +- properly configure `polymorphic_name` option -* honor configured schema on bulk create +- honor configured schema on bulk create ### Improvements: -* support `all_tenants?` option for identities +- support `all_tenants?` option for identities -* support `all_tenants?` option for custom indexes +- support `all_tenants?` option for custom indexes -* support join_filters on aggregates +- support join_filters on aggregates -* use the target action when generating related queries +- use the target action when generating related queries ## [v1.3.68](https://github.com/ash-project/ash_postgres/compare/v1.3.67...v1.3.68) (2024-01-04) - - - ### Bug Fixes: -* properly gather types for operator & function overloads +- properly gather types for operator & function overloads ## [v1.3.67](https://github.com/ash-project/ash_postgres/compare/v1.3.66...v1.3.67) (2024-01-04) - - - ### Bug Fixes: -* support encoding errors with expressions in them +- support encoding errors with expressions in them ### Improvements: -* support latest ash version & operator overrides +- support latest ash version & operator overrides -* support new bulk operations +- support new bulk operations ## [v1.3.66](https://github.com/ash-project/ash_postgres/compare/v1.3.65...v1.3.66) (2023-12-30) - - - ### Improvements: -* support new `return_query/2` callback +- support new `return_query/2` callback -* support new `:no_rollback` error signal +- support new `:no_rollback` error signal -* require `name` when generating migrations +- require `name` when generating migrations -* support directly referencing aggregates from aggregates +- support directly referencing aggregates from aggregates -* support aggregates as `get_path` subject +- support aggregates as `get_path` subject ## [v1.3.65](https://github.com/ash-project/ash_postgres/compare/v1.3.64...v1.3.65) (2023-12-23) - - - ### Bug Fixes: -* various fixes for unnecessary aggregate additions +- various fixes for unnecessary aggregate additions -* use lateral joins when joining to subquery w/ parent reference +- use lateral joins when joining to subquery w/ parent reference -* replace upsert field with source in EXCLUDED fragment (#187) +- replace upsert field with source in EXCLUDED fragment (#187) -* handle strings in get_path +- handle strings in get_path -* reenable mix tasks that need calling +- reenable mix tasks that need calling ### Improvements: -* support aggregates using other aggregates +- support aggregates using other aggregates -* support string_length and string_trim +- support string_length and string_trim -* only start savepoints when necessary +- only start savepoints when necessary -* clean up nested if statements to single case statements +- clean up nested if statements to single case statements -* support for `error/2` expression +- support for `error/2` expression ## [v1.3.64](https://github.com/ash-project/ash_postgres/compare/v1.3.63...v1.3.64) (2023-12-04) - - - ### Bug Fixes: -* properly cast lazy update defaults to target type +- properly cast lazy update defaults to target type ## [v1.3.63](https://github.com/ash-project/ash_postgres/compare/v1.3.62...v1.3.63) (2023-12-03) - - - ### Bug Fixes: -* use maps for composite_type instead of tuples +- use maps for composite_type instead of tuples -* avoid empty error on upserts with `:nothing` +- avoid empty error on upserts with `:nothing` -* simplify aggregate bindings & calculation reference building +- simplify aggregate bindings & calculation reference building -* hydrate aggregate refs when adding for calculations +- hydrate aggregate refs when adding for calculations -* apply limit to `from_many?` relationship joins +- apply limit to `from_many?` relationship joins -* properly add filters for exists aggregates +- properly add filters for exists aggregates -* properly expand calculation values across aggregate invocations +- properly expand calculation values across aggregate invocations -* don't add filter for `no_attributes?` relationships +- don't add filter for `no_attributes?` relationships -* handle `no_attributes?` flag on aggregates better +- handle `no_attributes?` flag on aggregates better -* properly handle sorted relationships in aggregates +- properly handle sorted relationships in aggregates ### Improvements: -* support `composite_type/2` expression +- support `composite_type/2` expression -* support composite types +- support composite types -* optimize relationships with identity on other end +- optimize relationships with identity on other end -* allow specifying multi-column foreign keys (#180) +- allow specifying multi-column foreign keys (#180) -* add match_with option on references +- add match_with option on references -* add match_type option on references +- add match_type option on references ## [v1.3.62](https://github.com/ash-project/ash_postgres/compare/v1.3.61...v1.3.62) (2023-11-16) - - - ### Bug Fixes: -* use `synonymous_relationship_path` when looking up ref bindings +- use `synonymous_relationship_path` when looking up ref bindings -* add calculation context to calculation expressions +- add calculation context to calculation expressions ## [v1.3.61](https://github.com/ash-project/ash_postgres/compare/v1.3.60...v1.3.61) (2023-11-15) - - - ### Bug Fixes: -* don't append update_defaults automatically if `upsert_fields` was set +- don't append update_defaults automatically if `upsert_fields` was set -* don't ensure repo compiled at compile time +- don't ensure repo compiled at compile time -* handle additional case for new functional repo callback +- handle additional case for new functional repo callback -* get resource from proper bindings on `exists` query +- get resource from proper bindings on `exists` query ### Improvements: -* support a 2 argument function for the repo option +- support a 2 argument function for the repo option -* spport `CURRENT_DATE` default +- spport `CURRENT_DATE` default ## [v1.3.60](https://github.com/ash-project/ash_postgres/compare/v1.3.59...v1.3.60) (2023-10-27) - - - ### Improvements: -* support `parent` in sort expressions +- support `parent` in sort expressions ## [v1.3.59](https://github.com/ash-project/ash_postgres/compare/v1.3.58...v1.3.59) (2023-10-25) - - - ### Improvements: -* join relationships for aggregate filters +- join relationships for aggregate filters ## [v1.3.58](https://github.com/ash-project/ash_postgres/compare/v1.3.57...v1.3.58) (2023-10-24) - - - ### Bug Fixes: -* don't traverse new types for storage type +- don't traverse new types for storage type -* properly join to related references in relationship filters +- properly join to related references in relationship filters ## [v1.3.57](https://github.com/ash-project/ash_postgres/compare/v1.3.56...v1.3.57) (2023-10-17) - - - ### Improvements: -* allow for combining `AshPostgres.Repo` with other repos +- allow for combining `AshPostgres.Repo` with other repos ## [v1.3.56](https://github.com/ash-project/ash_postgres/compare/v1.3.55...v1.3.56) (2023-10-11) - - - ### Bug Fixes: -* don't raise all errors +- don't raise all errors ## [v1.3.55](https://github.com/ash-project/ash_postgres/compare/v1.3.54...v1.3.55) (2023-10-11) - - - ### Improvements: -* support atomics on upserts +- support atomics on upserts ## [v1.3.54](https://github.com/ash-project/ash_postgres/compare/v1.3.53...v1.3.54) (2023-10-10) - - - ### Bug Fixes: -* fix type specification for foreign_key_names +- fix type specification for foreign_key_names ## [v1.3.53](https://github.com/ash-project/ash_postgres/compare/v1.3.52...v1.3.53) (2023-10-10) - - - ### Bug Fixes: -* don't run main query if only `exists` aggs are specified +- don't run main query if only `exists` aggs are specified -* subquery aggregate if limit is applied +- subquery aggregate if limit is applied ### Improvements: -* update ash dependency +- update ash dependency -* support `:ci_string` as a storage_type +- support `:ci_string` as a storage_type -* support to-one references in calculations +- support to-one references in calculations ## [v1.3.52](https://github.com/ash-project/ash_postgres/compare/v1.3.51...v1.3.52) (2023-09-26) - - - ### Bug Fixes: -* use `:wrap_list` type instead of custom validaitons (#167) +- use `:wrap_list` type instead of custom validaitons (#167) ### Improvements: -* fix `upsert_fields` behavior for upserts +- fix `upsert_fields` behavior for upserts -* support data_layer_context option on transactions +- support data_layer_context option on transactions ## [v1.3.51](https://github.com/ash-project/ash_postgres/compare/v1.3.50...v1.3.51) (2023-09-20) - - - ### Improvements: -* add `AshPostgres.Tsvector` +- add `AshPostgres.Tsvector` -* add AshPostgres.Tsquery +- add AshPostgres.Tsquery -* support vector types and `vector_cosine_distance` +- support vector types and `vector_cosine_distance` ## [v1.3.50](https://github.com/ash-project/ash_postgres/compare/v1.3.49...v1.3.50) (2023-09-06) - - - ### Improvements: -* Allow resources to opt out of the primary key requirement. (#166) +- Allow resources to opt out of the primary key requirement. (#166) ## [v1.3.49](https://github.com/ash-project/ash_postgres/compare/v1.3.48...v1.3.49) (2023-09-04) - - - ### Improvements: -* implement ash lifecycle tasks +- implement ash lifecycle tasks ## [v1.3.48](https://github.com/ash-project/ash_postgres/compare/v1.3.47...v1.3.48) (2023-09-04) - - - ### Improvements: -* better error message for missing table config +- better error message for missing table config ## [v1.3.47](https://github.com/ash-project/ash_postgres/compare/v1.3.46...v1.3.47) (2023-08-31) - - - ### Bug Fixes: -* ensure we always select at least one field, and change one field +- ensure we always select at least one field, and change one field ## [v1.3.46](https://github.com/ash-project/ash_postgres/compare/v1.3.45...v1.3.46) (2023-08-31) - - - ### Bug Fixes: -* use provided values for updates +- use provided values for updates ## [v1.3.45](https://github.com/ash-project/ash_postgres/compare/v1.3.44...v1.3.45) (2023-08-31) - - - ### Bug Fixes: -* don't clobber loaded data on update +- don't clobber loaded data on update ## [v1.3.44](https://github.com/ash-project/ash_postgres/compare/v1.3.43...v1.3.44) (2023-08-31) - - - ### Bug Fixes: -* properly handle ensure nsted calls to `get_path` are jsonb +- properly handle ensure nsted calls to `get_path` are jsonb ### Improvements: -* support atomics (#165) +- support atomics (#165) ## [v1.3.43](https://github.com/ash-project/ash_postgres/compare/v1.3.42...v1.3.43) (2023-08-22) - - - ### Bug Fixes: -* properly provide constraints on all type casting +- properly provide constraints on all type casting ## [v1.3.42](https://github.com/ash-project/ash_postgres/compare/v1.3.41...v1.3.42) (2023-08-22) - - - ### Bug Fixes: -* support non-atom named aggregates +- support non-atom named aggregates -* handle case where multiple grouped aggregates depend on further aggregates +- handle case where multiple grouped aggregates depend on further aggregates ### Improvements: -* support in-line aggregates +- support in-line aggregates -* specify @behaviour in AshPostgres.Type +- specify @behaviour in AshPostgres.Type -* add `value_to_postgres_default/3` and `AshPostgres.Type` +- add `value_to_postgres_default/3` and `AshPostgres.Type` -* handle non-cast-in-type queries +- handle non-cast-in-type queries ## [v1.3.41](https://github.com/ash-project/ash_postgres/compare/v1.3.40...v1.3.41) (2023-08-08) - - - ### Bug Fixes: -* handle interaction between distinct, join filters and sort +- handle interaction between distinct, join filters and sort ### Improvements: -* custom-extension implementation (#162) +- custom-extension implementation (#162) -* custom-extension implementation +- custom-extension implementation -* allow adding custom-extension by module's reference and fixes formatting +- allow adding custom-extension by module's reference and fixes formatting -* support new `from_many?` option +- support new `from_many?` option -* subquery after distinct to handle distinct +- subquery after distinct to handle distinct ## [v1.3.40](https://github.com/ash-project/ash_postgres/compare/v1.3.39...v1.3.40) (2023-08-01) - - - ### Bug Fixes: -* properly detect optimizable first aggregates +- properly detect optimizable first aggregates ## [v1.3.39](https://github.com/ash-project/ash_postgres/compare/v1.3.38...v1.3.39) (2023-08-01) - - - ### Bug Fixes: -* properly alter deferrability on attribute alter +- properly alter deferrability on attribute alter ### Improvements: -* update ash +- update ash -* handle empty maps in migration defaults automatically +- handle empty maps in migration defaults automatically -* handle empty lists in migraiton defaults automatically +- handle empty lists in migraiton defaults automatically -* apply sort in subqueries properly +- apply sort in subqueries properly -* handle `no_attributes?` better in more places +- handle `no_attributes?` better in more places -* support the new `parent/1` expr in relationships +- support the new `parent/1` expr in relationships -* explicitly lock the source row +- explicitly lock the source row ## [v1.3.38](https://github.com/ash-project/ash_postgres/compare/v1.3.37...v1.3.38) (2023-07-21) - - - ### Bug Fixes: -* un-break aggregates referencing calculations +- un-break aggregates referencing calculations ### Improvements: -* properly handle context for referenced calculations +- properly handle context for referenced calculations ## [v1.3.37](https://github.com/ash-project/ash_postgres/compare/v1.3.36...v1.3.37) (2023-07-19) - - - ### Improvements: -* support new `distinct_sort` option +- support new `distinct_sort` option ## [v1.3.36](https://github.com/ash-project/ash_postgres/compare/v1.3.35...v1.3.36) (2023-07-19) - - - ### Bug Fixes: -* type casting improvements, handle manual relationships in `exists` +- type casting improvements, handle manual relationships in `exists` -* protected names in conflict_target (#158) +- protected names in conflict_target (#158) ## [v1.3.35](https://github.com/ash-project/ash_postgres/compare/v1.3.34...v1.3.35) (2023-07-18) - - - ### Improvements: -* support new `distinct` features from ash core +- support new `distinct` features from ash core ## [v1.3.34](https://github.com/ash-project/ash_postgres/compare/v1.3.33...v1.3.34) (2023-07-18) - - - ### Improvements: -* support unary `-/1` operator +- support unary `-/1` operator ## [v1.3.33](https://github.com/ash-project/ash_postgres/compare/v1.3.32...v1.3.33) (2023-07-14) - - - ### Bug Fixes: -* convert `Ash.Resource.Aggregate` to `Ash.Query.Aggregate` when adding +- convert `Ash.Resource.Aggregate` to `Ash.Query.Aggregate` when adding ### Improvements: -* support `deferrable` option in migration generator +- support `deferrable` option in migration generator -* support `exists` aggregates +- support `exists` aggregates ## [v1.3.32](https://github.com/ash-project/ash_postgres/compare/v1.3.31...v1.3.32) (2023-07-12) - - - ### Improvements: -* support `at/2` expression +- support `at/2` expression ## [v1.3.31](https://github.com/ash-project/ash_postgres/compare/v1.3.30...v1.3.31) (2023-07-12) - - - ### Bug Fixes: -* raise better error on invalid filter values +- raise better error on invalid filter values -* Fixes multiple schema identities migrations (#156) +- Fixes multiple schema identities migrations (#156) -* fix Logger deprecations for elixir 1.15 (#155) +- fix Logger deprecations for elixir 1.15 (#155) -* interpolate table names with `inspect` in generated migrations (#152) +- interpolate table names with `inspect` in generated migrations (#152) ### Improvements: -* better `ash_functions` message +- better `ash_functions` message -* support `string_split` +- support `string_split` -* add postgres expressions guide +- add postgres expressions guide -* add `simple_join_first_aggregates` option +- add `simple_join_first_aggregates` option ## [v1.3.30](https://github.com/ash-project/ash_postgres/compare/v1.3.29...v1.3.30) (2023-06-06) - - - ### Bug Fixes: -* handle changing custom index names better +- handle changing custom index names better -* validate custom index names +- validate custom index names ## [v1.3.29](https://github.com/ash-project/ash_postgres/compare/v1.3.28...v1.3.29) (2023-06-05) - - - ### Bug Fixes: -* properly handle nested aggregate references +- properly handle nested aggregate references ## [v1.3.28](https://github.com/ash-project/ash_postgres/compare/v1.3.27...v1.3.28) (2023-05-23) - - - ### Bug Fixes: -* handle raised errors in bulk actions +- handle raised errors in bulk actions ## [v1.3.27](https://github.com/ash-project/ash_postgres/compare/v1.3.26...v1.3.27) (2023-05-17) - - - ### Improvements: -* raise better errors on conflicting locks +- raise better errors on conflicting locks ## [v1.3.26](https://github.com/ash-project/ash_postgres/compare/v1.3.25...v1.3.26) (2023-05-16) - - - ### Bug Fixes: -* use proper lock list again +- use proper lock list again -* use proper list of row level locks +- use proper list of row level locks -* check `changeset.action_type` not `changeset.action.type` +- check `changeset.action_type` not `changeset.action.type` ### Improvements: -* support more lock types +- support more lock types ## [v1.3.25](https://github.com/ash-project/ash_postgres/compare/v1.3.24...v1.3.25) (2023-05-08) - - - ### Improvements: -* support changeset.filters (for optimistic locking) +- support changeset.filters (for optimistic locking) ## [v1.3.24](https://github.com/ash-project/ash_postgres/compare/v1.3.23...v1.3.24) (2023-05-03) - - - ### Improvements: -* support bulk upserts +- support bulk upserts ## [v1.3.23](https://github.com/ash-project/ash_postgres/compare/v1.3.22...v1.3.23) (2023-05-01) - - - ### Bug Fixes: -* don't incorrectly mark references as primary key references +- don't incorrectly mark references as primary key references -* go back to old migration sorting algorithm +- go back to old migration sorting algorithm ## [v1.3.22](https://github.com/ash-project/ash_postgres/compare/v1.3.21...v1.3.22) (2023-04-28) - - - ### Improvements: -* support locking +- support locking ## [v1.3.21](https://github.com/ash-project/ash_postgres/compare/v1.3.20...v1.3.21) (2023-04-27) - - - ### Improvements: -* handle new spark versions better, more explicit snapshots +- handle new spark versions better, more explicit snapshots ## [v1.3.20](https://github.com/ash-project/ash_postgres/compare/v1.3.19...v1.3.20) (2023-04-22) - - - ### Bug Fixes: -* subquery aggregates when a distinct is being added +- subquery aggregates when a distinct is being added -* don't call `.table` on `nil` +- don't call `.table` on `nil` -* wrap `datetime_add` in parenthesis +- wrap `datetime_add` in parenthesis -* handle primary key changes properly +- handle primary key changes properly ### Improvements: -* update ash +- update ash -* don't call `.table` on `nil` `snapshot` +- don't call `.table` on `nil` `snapshot` -* use digraph for operation ordering +- use digraph for operation ordering ## [v1.3.19](https://github.com/ash-project/ash_postgres/compare/v1.3.18...v1.3.19) (2023-04-07) - - - ### Bug Fixes: -* properly handle newtypes, add test +- properly handle newtypes, add test -* honor newtypes when determining migration type +- honor newtypes when determining migration type -* handle nil ash_functions_version in another place +- handle nil ash_functions_version in another place -* handle nil ash_functions_version +- handle nil ash_functions_version ### Improvements: -* update ash +- update ash ## [v1.3.18](https://github.com/ash-project/ash_postgres/compare/v1.3.17...v1.3.18) (2023-03-23) - - - ## [v1.3.17](https://github.com/ash-project/ash_postgres/compare/v1.3.16...v1.3.17) (2023-03-20) - - - ### Bug Fixes: -* properly map `parent` bindings in `exists` +- properly map `parent` bindings in `exists` ## [v1.3.16](https://github.com/ash-project/ash_postgres/compare/v1.3.15...v1.3.16) (2023-03-03) - - - ### Improvements: -* support new date expressions +- support new date expressions ## [v1.3.15](https://github.com/ash-project/ash_postgres/compare/v1.3.14...v1.3.15) (2023-02-23) - - - ### Improvements: -* add aggregates used by sorts +- add aggregates used by sorts ## [v1.3.14](https://github.com/ash-project/ash_postgres/compare/v1.3.13...v1.3.14) (2023-02-21) - - - ### Improvements: -* Implement string_join expr (#132) +- Implement string_join expr (#132) ## [v1.3.13](https://github.com/ash-project/ash_postgres/compare/v1.3.12...v1.3.13) (2023-02-17) - - - ### Bug Fixes: -* don't use `:distinct` when `uniq?` is not `true` +- don't use `:distinct` when `uniq?` is not `true` ## [v1.3.12](https://github.com/ash-project/ash_postgres/compare/v1.3.11...v1.3.12) (2023-02-16) - - - ### Bug Fixes: -* exclude `order_by` when building aggregates +- exclude `order_by` when building aggregates ## [v1.3.11](https://github.com/ash-project/ash_postgres/compare/v1.3.10...v1.3.11) (2023-02-16) - - - ### Bug Fixes: -* properly find migration directories in umbrella apps +- properly find migration directories in umbrella apps -* don't double-cast to array for list aggregates +- don't double-cast to array for list aggregates ### Improvements: -* significantly optimize aggregate queries +- significantly optimize aggregate queries -* better type casting for concat operator +- better type casting for concat operator ## [v1.3.10](https://github.com/ash-project/ash_postgres/compare/v1.3.9...v1.3.10) (2023-02-09) - - - ### Bug Fixes: -* sorting on optimized first aggregates +- sorting on optimized first aggregates ## [v1.3.9](https://github.com/ash-project/ash_postgres/compare/v1.3.8...v1.3.9) (2023-02-09) - - - ### Bug Fixes: -* do limit/offset outside of query if distinct is required +- do limit/offset outside of query if distinct is required -* load by __order__ ascending +- load by **order** ascending ### Improvements: -* support new `uniq?` option on count/list aggregates +- support new `uniq?` option on count/list aggregates -* optimized `first` aggregates where possible +- optimized `first` aggregates where possible ## [v1.3.8](https://github.com/ash-project/ash_postgres/compare/v1.3.7...v1.3.8) (2023-02-06) - - - ### Bug Fixes: -* Actually use `AshPostgres.Repo` behaviour (#129) +- Actually use `AshPostgres.Repo` behaviour (#129) ### Improvements: -* authorization filters are now attached by ash core +- authorization filters are now attached by ash core ## [v1.3.7](https://github.com/ash-project/ash_postgres/compare/v1.3.6...v1.3.7) (2023-02-06) - - - ### Bug Fixes: -* Actually use `AshPostgres.Repo` behaviour (#129) +- Actually use `AshPostgres.Repo` behaviour (#129) ### Improvements: -* authorization filters are now attached by ash core +- authorization filters are now attached by ash core ## [v1.3.6](https://github.com/ash-project/ash_postgres/compare/v1.3.5...v1.3.6) (2023-02-03) - - - ### Bug Fixes: -* properly set next migration name +- properly set next migration name -* override `insert` function for proper ecto interop +- override `insert` function for proper ecto interop ### Improvements: -* add `migration_ignore_attributes` +- add `migration_ignore_attributes` ## [v1.3.5](https://github.com/ash-project/ash_postgres/compare/v1.3.4...v1.3.5) (2023-01-29) - - - ### Bug Fixes: -* properly convert to/from ecto, only when necessary +- properly convert to/from ecto, only when necessary ## [v1.3.4](https://github.com/ash-project/ash_postgres/compare/v1.3.3...v1.3.4) (2023-01-28) - - - ### Bug Fixes: -* support latest ecto interop changes in ash core +- support latest ecto interop changes in ash core ### Improvements: -* properly cast division to floats for elixir-y behavior +- properly cast division to floats for elixir-y behavior -* support for dynamically set repo +- support for dynamically set repo -* update ash +- update ash ## [v1.3.3](https://github.com/ash-project/ash_postgres/compare/v1.3.2...v1.3.3) (2023-01-18) - - - ### Improvements: -* update to new docs patterns +- update to new docs patterns ## [v1.3.2](https://github.com/ash-project/ash_postgres/compare/v1.3.1...v1.3.2) (2023-01-17) - - - ### Bug Fixes: -* nest subqueries when required for distinct +- nest subqueries when required for distinct -* replace `{:in, ...}` type with `{:array, ...}` +- replace `{:in, ...}` type with `{:array, ...}` ## [v1.3.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0...v1.3.1) (2023-01-11) - - - ### Bug Fixes: -* allow for non attribute aggregate references for first/list +- allow for non attribute aggregate references for first/list ## [v1.3.0](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.4...v1.3.0) (2023-01-11) - - - ### Improvements: -* update to latest ash +- update to latest ash ## [v1.3.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.3...v1.3.0-rc.4) (2023-01-09) - - - ### Bug Fixes: -* properly join to all required relationships +- properly join to all required relationships ## [v1.3.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.2...v1.3.0-rc.3) (2023-01-09) - - - ### Bug Fixes: -* properly type cast in fragments (and elsewhere) +- properly type cast in fragments (and elsewhere) ## [v1.3.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.1...v1.3.0-rc.2) (2023-01-06) - - - ### Bug Fixes: -* undo changes that caused type casting bugs +- undo changes that caused type casting bugs ## [v1.3.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.0...v1.3.0-rc.1) (2023-01-06) - - - ### Bug Fixes: -* undo changes that caused type casting bugs +- undo changes that caused type casting bugs ## [v1.3.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.0...v1.3.0-rc.1) (2023-01-06) - - - ### Bug Fixes: -* use `parent_expr` instead of `this` +- use `parent_expr` instead of `this` -* various expression & type building fixes +- various expression & type building fixes ## [v1.3.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.2.6...v1.3.0-rc.0) (2023-01-04) - - - ### Features: -* support latest ash +- support latest ash ### Bug Fixes: -* honor calculation constraints +- honor calculation constraints -* handle lists with expressions inside +- handle lists with expressions inside ### Improvements: -* support calc constraints +- support calc constraints -* support new `cast_in_query?/2` +- support new `cast_in_query?/2` -* support calculations as aggregate targets +- support calculations as aggregate targets ## [v1.2.6](https://github.com/ash-project/ash_postgres/compare/v1.2.5...v1.2.6) (2022-12-27) - - - ### Bug Fixes: -* properly set `migrations_path` default in umbrellas +- properly set `migrations_path` default in umbrellas -* don't subquery unless we have to +- don't subquery unless we have to ## [v1.2.5](https://github.com/ash-project/ash_postgres/compare/v1.2.4...v1.2.5) (2022-12-21) - - - ### Bug Fixes: -* don't group aggregates that reference relationships in their filters +- don't group aggregates that reference relationships in their filters -* properly skip unique indexes when configured +- properly skip unique indexes when configured ### Improvements: -* add like and ilike +- add like and ilike ## [v1.2.4](https://github.com/ash-project/ash_postgres/compare/v1.2.3...v1.2.4) (2022-12-18) - - - ### Bug Fixes: -* properly add aggregates to query when referenced from calculations +- properly add aggregates to query when referenced from calculations ### Improvements: -* distinct on source of query, not relationship destination +- distinct on source of query, not relationship destination ## [v1.2.3](https://github.com/ash-project/ash_postgres/compare/v1.2.2...v1.2.3) (2022-12-15) - - - ### Bug Fixes: -* properly combine sort + to many join filter +- properly combine sort + to many join filter ## [v1.2.2](https://github.com/ash-project/ash_postgres/compare/v1.2.1...v1.2.2) (2022-12-15) - - - ### Improvements: -* udpate to latest ash, fix array issues +- udpate to latest ash, fix array issues ## [v1.2.1](https://github.com/ash-project/ash_postgres/compare/v1.2.0...v1.2.1) (2022-12-13) - - - ### Bug Fixes: -* pattern match error in `lazy_non_matching_defaults/1` +- pattern match error in `lazy_non_matching_defaults/1` -* use attribute name not attribute for default funs +- use attribute name not attribute for default funs -* *actually* fix `default_fun` upserts +- _actually_ fix `default_fun` upserts -* fix upserting update_defaults +- fix upserting update_defaults ## [v1.2.0](https://github.com/ash-project/ash_postgres/compare/v1.2.0-rc.1...v1.2.0) (2022-12-13) - - - ### Bug Fixes: -* make migration generator work better for umbrellas +- make migration generator work better for umbrellas ## [v1.2.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.2.0-rc.0...v1.2.0-rc.1) (2022-12-10) - - - ### Bug Fixes: -* don't make migration generation recursive +- don't make migration generation recursive -* nevermind, can't make migrate recursive +- nevermind, can't make migrate recursive ### Improvements: -* make migrate task recursive as well +- make migrate task recursive as well -* mark generate_migrations as recursive for umbrellas +- mark generate_migrations as recursive for umbrellas ## [v1.2.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.1.3...v1.2.0-rc.0) (2022-12-10) - - - ### Features: -* avg/min/max/custom aggregate support +- avg/min/max/custom aggregate support ### Bug Fixes: -* various broken behavior from new aggregate work +- various broken behavior from new aggregate work -* forgot a +- forgot a -* fix various problems with the model behind aggregates +- fix various problems with the model behind aggregates -* properly set binding names for many to many join filters +- properly set binding names for many to many join filters ### Improvements: -* better error messages from mix tasks +- better error messages from mix tasks -* validate that references refer to relationships +- validate that references refer to relationships -* avg/min/max/custom aggregate support +- avg/min/max/custom aggregate support -* upgrade and depend on ash version +- upgrade and depend on ash version -* fix lateral many to many joins +- fix lateral many to many joins -* inform users about postgres incompatibility with multidimensional arrays +- inform users about postgres incompatibility with multidimensional arrays ## [v1.1.3](https://github.com/ash-project/ash_postgres/compare/v1.1.2...v1.1.3) (2022-12-01) - - - ### Bug Fixes: -* properly turn custom index keys into atoms +- properly turn custom index keys into atoms ### Improvements: -* update ash, add test for transaction hooks +- update ash, add test for transaction hooks -* support new transaction info with hooks +- support new transaction info with hooks -* add unique constraints to changeset for custom unique indexes +- add unique constraints to changeset for custom unique indexes -* separate out concurrent index creations and do them in a separate transaction +- separate out concurrent index creations and do them in a separate transaction ## [v1.1.2](https://github.com/ash-project/ash_postgres/compare/v1.1.1...v1.1.2) (2022-11-21) - - - ### Bug Fixes: -* don't use hard-coded join assoc name (#118) +- don't use hard-coded join assoc name (#118) ### Improvements: -* add `migration_defaults` for customizing default values +- add `migration_defaults` for customizing default values ## [v1.1.1](https://github.com/ash-project/ash_postgres/compare/v1.1.0...v1.1.1) (2022-10-25) - - - ### Bug Fixes: -* && operator in expressions to point to ash_elixir_and (#115) +- && operator in expressions to point to ash_elixir_and (#115) ### Improvements: -* add check for unsupported expression +- add check for unsupported expression ## [v1.1.0](https://github.com/ash-project/ash_postgres/compare/v1.0.0...v1.1.0) (2022-10-20) - - - ### Features: -* support `now()` in latest Ash +- support `now()` in latest Ash ## [v1.0.0](https://github.com/ash-project/ash_postgres/compare/v0.43.0...v1.0.0) (2022-10-17) - ### Bug Fixes: -* no unnecessary type cast on count/sum aggregates +- no unnecessary type cast on count/sum aggregates -* don't apply `filter` to `array_agg` +- don't apply `filter` to `array_agg` ### Improvements: -* update to Ash 2.0 +- update to Ash 2.0 -* handle UUID types better +- handle UUID types better -* set lateral join source for latest ash +- set lateral join source for latest ash -* use `prepend?: true` option when applying relationship sorts +- use `prepend?: true` option when applying relationship sorts ## [v1.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.8...v1.0.0-rc.9) (2022-10-07) - - - ### Bug Fixes: -* handle custom calculation selects properly +- handle custom calculation selects properly -* use attribute source for identity fields +- use attribute source for identity fields ### Improvements: -* update to the latest ash +- update to the latest ash -* remove the need to dynamically expand fragments +- remove the need to dynamically expand fragments -* when casting string to uuid, dump to binary +- when casting string to uuid, dump to binary -* update to latest ash +- update to latest ash ## [v1.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.7...v1.0.0-rc.8) (2022-09-29) - - - ### Bug Fixes: -* never attempt to group custom operations +- never attempt to group custom operations -* wrap case statement in parens +- wrap case statement in parens ### Improvements: -* `exists` filters necessitate multiple aggregate joins (for now) +- `exists` filters necessitate multiple aggregate joins (for now) ## [v1.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.6...v1.0.0-rc.7) (2022-09-28) - - - ### Bug Fixes: -* properly type cast top level fragments +- properly type cast top level fragments ### Improvements: -* update to the latest ash +- update to the latest ash -* upgrade to new `exists` usage +- upgrade to new `exists` usage ## [v1.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.5...v1.0.0-rc.6) (2022-09-21) - - - ### Improvements: -* support latest ash +- support latest ash ## [v1.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.4...v1.0.0-rc.5) (2022-09-15) - - - ### Improvements: -* update to latest ash +- update to latest ash -* implement Length function (#111) +- implement Length function (#111) -* upgrade to latest ash +- upgrade to latest ash ## [v1.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.3...v1.0.0-rc.4) (2022-09-14) - - - ### Improvements: -* support latest ash +- support latest ash -* support manual relationships with joins +- support manual relationships with joins ## [v1.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.2...v1.0.0-rc.3) (2022-09-12) - - - ### Bug Fixes: -* keep unique index keys in order in migrations +- keep unique index keys in order in migrations ## [v1.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.1...v1.0.0-rc.2) (2022-09-06) - - - ### Improvements: -* support latest ash `exists/2` expr +- support latest ash `exists/2` expr ## [v1.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.0...v1.0.0-rc.1) (2022-09-04) - - - ## [v0.43.0](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.7...v0.43.0) (2022-08-05) - - - ### Bug Fixes: -* properly order check constraints +- properly order check constraints -* remove check constraints before adding them +- remove check constraints before adding them ### Improvements: -* fix typecasting for calculations & embed access +- fix typecasting for calculations & embed access -* add custom_statements to migration generator +- add custom_statements to migration generator -* support `||` and `&&` +- support `||` and `&&` ## [v0.42.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.6...v0.42.0-rc.7) (2022-07-14) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* use new doc_index patterns +- use new doc_index patterns -* support upsert_identity with base_filter +- support upsert_identity with base_filter -* support upsert_identity with base filters +- support upsert_identity with base filters -* handle various join bugs +- handle various join bugs -* use attribute.name if attribute.source is nil +- use attribute.name if attribute.source is nil -* set attribute source properly +- set attribute source properly -* ensure source is always set on attributes in snapshots +- ensure source is always set on attributes in snapshots -* handle paths for aggregates w/ > 2 relationships +- handle paths for aggregates w/ > 2 relationships -* rename attributes correctly in down migration (#98) +- rename attributes correctly in down migration (#98) -* don't generate modify commands for attributes due to schema changes +- don't generate modify commands for attributes due to schema changes -* default schema to primary schema +- default schema to primary schema -* test and confirm behavior of schemas +- test and confirm behavior of schemas -* use correct bindings for filtered relationships +- use correct bindings for filtered relationships -* cast calcs in query expressions +- cast calcs in query expressions -* explicitly type cast aggregate/calc selects +- explicitly type cast aggregate/calc selects -* don't try and match reference schema to table schema +- don't try and match reference schema to table schema -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* add default guide, and empty ash postgres guide +- add default guide, and empty ash postgres guide -* set `update_defaults` on upsert results +- set `update_defaults` on upsert results -* handle fallback ecto migration default elegantly (#94) +- handle fallback ecto migration default elegantly (#94) -* add `ignore?` option to `references` +- add `ignore?` option to `references` -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.5...v0.42.0-rc.6) (2022-07-10) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* use new doc_index patterns +- use new doc_index patterns -* support upsert_identity with base_filter +- support upsert_identity with base_filter -* support upsert_identity with base filters +- support upsert_identity with base filters -* handle various join bugs +- handle various join bugs -* use attribute.name if attribute.source is nil +- use attribute.name if attribute.source is nil -* set attribute source properly +- set attribute source properly -* ensure source is always set on attributes in snapshots +- ensure source is always set on attributes in snapshots -* handle paths for aggregates w/ > 2 relationships +- handle paths for aggregates w/ > 2 relationships -* rename attributes correctly in down migration (#98) +- rename attributes correctly in down migration (#98) -* don't generate modify commands for attributes due to schema changes +- don't generate modify commands for attributes due to schema changes -* default schema to primary schema +- default schema to primary schema -* test and confirm behavior of schemas +- test and confirm behavior of schemas -* use correct bindings for filtered relationships +- use correct bindings for filtered relationships -* cast calcs in query expressions +- cast calcs in query expressions -* explicitly type cast aggregate/calc selects +- explicitly type cast aggregate/calc selects -* don't try and match reference schema to table schema +- don't try and match reference schema to table schema -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* set `update_defaults` on upsert results +- set `update_defaults` on upsert results -* handle fallback ecto migration default elegantly (#94) +- handle fallback ecto migration default elegantly (#94) -* add `ignore?` option to `references` +- add `ignore?` option to `references` -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.4...v0.42.0-rc.5) (2022-07-06) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* support upsert_identity with base_filter +- support upsert_identity with base_filter -* support upsert_identity with base filters +- support upsert_identity with base filters -* handle various join bugs +- handle various join bugs -* use attribute.name if attribute.source is nil +- use attribute.name if attribute.source is nil -* set attribute source properly +- set attribute source properly -* ensure source is always set on attributes in snapshots +- ensure source is always set on attributes in snapshots -* handle paths for aggregates w/ > 2 relationships +- handle paths for aggregates w/ > 2 relationships -* rename attributes correctly in down migration (#98) +- rename attributes correctly in down migration (#98) -* don't generate modify commands for attributes due to schema changes +- don't generate modify commands for attributes due to schema changes -* default schema to primary schema +- default schema to primary schema -* test and confirm behavior of schemas +- test and confirm behavior of schemas -* use correct bindings for filtered relationships +- use correct bindings for filtered relationships -* cast calcs in query expressions +- cast calcs in query expressions -* explicitly type cast aggregate/calc selects +- explicitly type cast aggregate/calc selects -* don't try and match reference schema to table schema +- don't try and match reference schema to table schema -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* set `update_defaults` on upsert results. For most users, this means that where previously `updated_at` would not get set on an upsert that ultimately resulted in an update, it will now. +- set `update_defaults` on upsert results. For most users, this means that where previously `updated_at` would not get set on an upsert that ultimately resulted in an update, it will now. -* handle fallback ecto migration default elegantly (#94) +- handle fallback ecto migration default elegantly (#94) -* add `ignore?` option to `references` +- add `ignore?` option to `references` -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.3...v0.42.0-rc.4) (2022-06-28) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* use attribute.name if attribute.source is nil +- use attribute.name if attribute.source is nil -* set attribute source properly +- set attribute source properly -* ensure source is always set on attributes in snapshots +- ensure source is always set on attributes in snapshots -* handle paths for aggregates w/ > 2 relationships +- handle paths for aggregates w/ > 2 relationships -* rename attributes correctly in down migration (#98) +- rename attributes correctly in down migration (#98) -* don't generate modify commands for attributes due to schema changes +- don't generate modify commands for attributes due to schema changes -* default schema to primary schema +- default schema to primary schema -* test and confirm behavior of schemas +- test and confirm behavior of schemas -* use correct bindings for filtered relationships +- use correct bindings for filtered relationships -* cast calcs in query expressions +- cast calcs in query expressions -* explicitly type cast aggregate/calc selects +- explicitly type cast aggregate/calc selects -* don't try and match reference schema to table schema +- don't try and match reference schema to table schema -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* handle fallback ecto migration default elegantly (#94) +- handle fallback ecto migration default elegantly (#94) -* add `ignore?` option to `references` +- add `ignore?` option to `references` -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.2...v0.42.0-rc.3) (2022-06-28) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* set attribute source properly +- set attribute source properly -* ensure source is always set on attributes in snapshots +- ensure source is always set on attributes in snapshots -* handle paths for aggregates w/ > 2 relationships +- handle paths for aggregates w/ > 2 relationships -* rename attributes correctly in down migration (#98) +- rename attributes correctly in down migration (#98) -* don't generate modify commands for attributes due to schema changes +- don't generate modify commands for attributes due to schema changes -* default schema to primary schema +- default schema to primary schema -* test and confirm behavior of schemas +- test and confirm behavior of schemas -* use correct bindings for filtered relationships +- use correct bindings for filtered relationships -* cast calcs in query expressions +- cast calcs in query expressions -* explicitly type cast aggregate/calc selects +- explicitly type cast aggregate/calc selects -* don't try and match reference schema to table schema +- don't try and match reference schema to table schema -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* handle fallback ecto migration default elegantly (#94) +- handle fallback ecto migration default elegantly (#94) -* add `ignore?` option to `references` +- add `ignore?` option to `references` -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.1...v0.42.0-rc.2) (2022-05-18) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* don't try and match reference schema to table schema +- don't try and match reference schema to table schema -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.0...v0.42.0-rc.1) (2022-05-18) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* don't use `table` where we should use `schema` in migration generator +- don't use `table` where we should use `schema` in migration generator -* handle combinations of distinct & sort +- handle combinations of distinct & sort -* ensure all single actions are explicitly marked as primary? (#95) +- ensure all single actions are explicitly marked as primary? (#95) -* only rename schema when necessary +- only rename schema when necessary -* inspect un-defaultable value in error message +- inspect un-defaultable value in error message -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* check_migrations, rename to `--check` +- check_migrations, rename to `--check` -* add explicit timeout capability declaration +- add explicit timeout capability declaration -* add static schema specification in DSL +- add static schema specification in DSL -* support static schema specification in migration generator +- support static schema specification in migration generator -* implement decimal ecto migration default (#91) +- implement decimal ecto migration default (#91) -* support float as Ecto migration default (#89) +- support float as Ecto migration default (#89) -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.42.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.41.7...v0.42.0-rc.0) (2022-04-26) - - - ### Features: -* support `cast_in_query?/0` and `source` +- support `cast_in_query?/0` and `source` ### Bug Fixes: -* select custom aggregates properly +- select custom aggregates properly -* don't add reference when renaming column if unnecessary +- don't add reference when renaming column if unnecessary -* don't cast `nil` to `""` +- don't cast `nil` to `""` -* `!is_atom/1` -> `!is_boolean/1` +- `!is_atom/1` -> `!is_boolean/1` -* sanitize lists to stringify atoms +- sanitize lists to stringify atoms -* cast embedded atoms to strings first +- cast embedded atoms to strings first -* don't cast `{:in, :any}` types +- don't cast `{:in, :any}` types -* more don't cast any types +- more don't cast any types -* don't cast if there is no type +- don't cast if there is no type -* properly handle relationship filter bindings +- properly handle relationship filter bindings -* don't consider fields changed with only source -> name changes +- don't consider fields changed with only source -> name changes -* handle name -> source change in more places +- handle name -> source change in more places -* handle name -> source rename in operation ordering +- handle name -> source rename in operation ordering -* fix aggregate/base filters +- fix aggregate/base filters -* don't select more fields than necessary +- don't select more fields than necessary -* don't call `ecto_type` twice when resolving types +- don't call `ecto_type` twice when resolving types -* place expressions in the proper order in selects +- place expressions in the proper order in selects -* match on count in expr +- match on count in expr -* remove incorrect param count tracking +- remove incorrect param count tracking -* properly track param count +- properly track param count -* properly reverse parameters before/after expansion +- properly reverse parameters before/after expansion -* don't use the base ecto type +- don't use the base ecto type -* don't sort when joining +- don't sort when joining -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* update ecto +- update ecto -* add atom impl for `EctoMigrationDefault` +- add atom impl for `EctoMigrationDefault` -* Add EctoMigrationDefault protocol and implement defaults (#87) +- Add EctoMigrationDefault protocol and implement defaults (#87) -* update ecto, fix dialyzer +- update ecto, fix dialyzer -* support new timeouts +- support new timeouts -* make select unique before running query +- make select unique before running query -* add doc_index +- add doc_index -* add exclusion_constraint_names (#83) +- add exclusion_constraint_names (#83) -* support referencing aggregates from aggregate filters +- support referencing aggregates from aggregate filters -* support access syntax +- support access syntax -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.7](https://github.com/ash-project/ash_postgres/compare/v0.41.6...v0.41.7) (2021-12-21) - - - ### Bug Fixes: -* ensure repo is compiled (#80) +- ensure repo is compiled (#80) -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.6](https://github.com/ash-project/ash_postgres/compare/v0.41.5...v0.41.6) (2021-12-21) - - - ### Bug Fixes: -* properly construct nested join relationships +- properly construct nested join relationships -* use `CiStringWrapper` type in ash_postgres +- use `CiStringWrapper` type in ash_postgres -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.5](https://github.com/ash-project/ash_postgres/compare/v0.41.4...v0.41.5) (2021-11-26) - - - ### Bug Fixes: -* ensure we are returning * on upserts (#79) +- ensure we are returning \* on upserts (#79) -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.4](https://github.com/ash-project/ash_postgres/compare/v0.41.3...v0.41.4) (2021-11-25) - - - ### Bug Fixes: -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* don't upsert defaults on conflict (#77) +- don't upsert defaults on conflict (#77) -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.3](https://github.com/ash-project/ash_postgres/compare/v0.41.2...v0.41.3) (2021-11-13) - - - ### Bug Fixes: -* handle new if types +- handle new if types -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* relax ash version requirement +- relax ash version requirement -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.2](https://github.com/ash-project/ash_postgres/compare/v0.41.1...v0.41.2) (2021-11-10) - - - ### Bug Fixes: -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* add custom migration types, and repo level override +- add custom migration types, and repo level override -* update to latest version of ash +- update to latest version of ash ## [v0.41.1](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.9...v0.41.1) (2021-11-03) - - - ### Bug Fixes: -* copy query prefix to newly created query (#74) +- copy query prefix to newly created query (#74) ### Improvements: -* update to latest version of ash +- update to latest version of ash ## [v0.41.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.8...v0.41.0-rc.9) (2021-11-01) - - - ### Bug Fixes: -* use proper ecto types everywhere +- use proper ecto types everywhere -* try to fix missing paren issue in array_agg +- try to fix missing paren issue in array_agg -* fix can? for :joins (#73) +- fix can? for :joins (#73) -* remove unused default value +- remove unused default value -* use proper identity names for polymorphic resources +- use proper identity names for polymorphic resources -* set identity names propertly for polymorphic resources +- set identity names propertly for polymorphic resources -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `default` on aggregates +- support `default` on aggregates -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.7...v0.41.0-rc.8) (2021-10-25) - - - ### Bug Fixes: -* fix can? for :joins (#73) +- fix can? for :joins (#73) -* remove unused default value +- remove unused default value -* use proper identity names for polymorphic resources +- use proper identity names for polymorphic resources -* set identity names propertly for polymorphic resources +- set identity names propertly for polymorphic resources -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `default` on aggregates +- support `default` on aggregates -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.6...v0.41.0-rc.7) (2021-10-24) - - - ### Bug Fixes: -* fix can? for :joins (#73) +- fix can? for :joins (#73) -* remove unused default value +- remove unused default value -* use proper identity names for polymorphic resources +- use proper identity names for polymorphic resources -* set identity names propertly for polymorphic resources +- set identity names propertly for polymorphic resources -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.5...v0.41.0-rc.6) (2021-09-26) - - - ### Bug Fixes: -* remove unused default value +- remove unused default value -* use proper identity names for polymorphic resources +- use proper identity names for polymorphic resources -* set identity names propertly for polymorphic resources +- set identity names propertly for polymorphic resources -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.4...v0.41.0-rc.5) (2021-09-21) - - - ### Bug Fixes: -* use proper identity names for polymorphic resources +- use proper identity names for polymorphic resources -* set identity names propertly for polymorphic resources +- set identity names propertly for polymorphic resources -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.3...v0.41.0-rc.4) (2021-09-21) - - - ### Bug Fixes: -* set identity names propertly for polymorphic resources +- set identity names propertly for polymorphic resources -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.2...v0.41.0-rc.3) (2021-09-21) - - - ### Bug Fixes: -* handle nil values in snapshots better +- handle nil values in snapshots better -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.1...v0.41.0-rc.2) (2021-09-21) - - - ### Bug Fixes: -* remove unused field from snapshot parsing +- remove unused field from snapshot parsing ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.0...v0.41.0-rc.1) (2021-09-20) - - - ### Improvements: -* support `custom_indexes` +- support `custom_indexes` ## [v0.41.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.40.11...v0.41.0-rc.0) (2021-09-13) -### Breaking Changes: - -* update to latest ash/ecto versions w/ parameterized types +### Breaking Changes: +- update to latest ash/ecto versions w/ parameterized types ### Improvements: -* Support default tenant migration path in releases (#69) +- Support default tenant migration path in releases (#69) ## [v0.40.11](https://github.com/ash-project/ash_postgres/compare/v0.40.10...v0.40.11) (2021-07-28) - - - ### Bug Fixes: -* set subquery prefix properly +- set subquery prefix properly ## [v0.40.10](https://github.com/ash-project/ash_postgres/compare/v0.40.9...v0.40.10) (2021-07-27) - - - ### Bug Fixes: -* set subquery source correctly +- set subquery source correctly -* create parameter for ci strings +- create parameter for ci strings -* explicitly set prefix at each level +- explicitly set prefix at each level -* interaction w/ attribute and context tenancy +- interaction w/ attribute and context tenancy ### Improvements: -* info on migration generator output +- info on migration generator output -* use match: :full on attr multitenancy +- use match: :full on attr multitenancy -* update to latest ash +- update to latest ash -* update to latest ash +- update to latest ash -* upgrade ash dep +- upgrade ash dep ## [v0.40.9](https://github.com/ash-project/ash_postgres/compare/v0.40.8...v0.40.9) (2021-07-22) - - - ### Bug Fixes: -* don't add a non-list to a list +- don't add a non-list to a list ### Improvements: -* add sort + select test +- add sort + select test ## [v0.40.8](https://github.com/ash-project/ash_postgres/compare/v0.40.7...v0.40.8) (2021-07-19) - - - ### Bug Fixes: -* ensure source table is sorted in lateral join +- ensure source table is sorted in lateral join ### Improvements: -* fix significant performance issue in lateral joins +- fix significant performance issue in lateral joins ## [v0.40.7](https://github.com/ash-project/ash_postgres/compare/v0.40.6...v0.40.7) (2021-07-12) - - - ### Improvements: -* support default_prefix configuration +- support default_prefix configuration ## [v0.40.6](https://github.com/ash-project/ash_postgres/compare/v0.40.5...v0.40.6) (2021-07-08) - - - ### Bug Fixes: -* fix migrator mix tasks w/ only/except tenants +- fix migrator mix tasks w/ only/except tenants -* drop foreign keys after table create properly +- drop foreign keys after table create properly -* drop foreign keys before dropping table +- drop foreign keys before dropping table -* left_lateral_join for many_to_many aggregates +- left_lateral_join for many_to_many aggregates -* properly reference nested aggregate fields for join +- properly reference nested aggregate fields for join -* properly determine fallback table for polymorphic resources +- properly determine fallback table for polymorphic resources -* ensure non-tenant resources can be aggregates +- ensure non-tenant resources can be aggregates -* properly set aggregate query sources +- properly set aggregate query sources -* retain parent as bindings +- retain parent as bindings -* don't add `rel_source` at all +- don't add `rel_source` at all -* properly build atoms list +- properly build atoms list -* horribly hack ecto for dynamic bindings +- horribly hack ecto for dynamic bindings -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* `--name` when generating migrations +- `--name` when generating migrations -* add `mix ash_postgres.rollback` +- add `mix ash_postgres.rollback` -* update to latest ash +- update to latest ash -* update to latest ash +- update to latest ash -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.5](https://github.com/ash-project/ash_postgres/compare/v0.40.4...v0.40.5) (2021-07-08) - - - ### Bug Fixes: -* fix migrator mix tasks w/ only/except tenants +- fix migrator mix tasks w/ only/except tenants -* drop foreign keys after table create properly +- drop foreign keys after table create properly -* drop foreign keys before dropping table +- drop foreign keys before dropping table -* left_lateral_join for many_to_many aggregates +- left_lateral_join for many_to_many aggregates -* properly reference nested aggregate fields for join +- properly reference nested aggregate fields for join -* properly determine fallback table for polymorphic resources +- properly determine fallback table for polymorphic resources -* ensure non-tenant resources can be aggregates +- ensure non-tenant resources can be aggregates -* properly set aggregate query sources +- properly set aggregate query sources -* retain parent as bindings +- retain parent as bindings -* don't add `rel_source` at all +- don't add `rel_source` at all -* properly build atoms list +- properly build atoms list -* horribly hack ecto for dynamic bindings +- horribly hack ecto for dynamic bindings -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* add `mix ash_postgres.rollback` +- add `mix ash_postgres.rollback` -* update to latest ash +- update to latest ash -* update to latest ash +- update to latest ash -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.4](https://github.com/ash-project/ash_postgres/compare/v0.40.3...v0.40.4) (2021-07-05) - - - ### Bug Fixes: -* left_lateral_join for many_to_many aggregates +- left_lateral_join for many_to_many aggregates -* properly reference nested aggregate fields for join +- properly reference nested aggregate fields for join -* properly determine fallback table for polymorphic resources +- properly determine fallback table for polymorphic resources -* ensure non-tenant resources can be aggregates +- ensure non-tenant resources can be aggregates -* properly set aggregate query sources +- properly set aggregate query sources -* retain parent as bindings +- retain parent as bindings -* don't add `rel_source` at all +- don't add `rel_source` at all -* properly build atoms list +- properly build atoms list -* horribly hack ecto for dynamic bindings +- horribly hack ecto for dynamic bindings -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* update to latest ash +- update to latest ash -* update to latest ash +- update to latest ash -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.3](https://github.com/ash-project/ash_postgres/compare/v0.40.2...v0.40.3) (2021-07-03) - - - ### Bug Fixes: -* ensure non-tenant resources can be aggregates +- ensure non-tenant resources can be aggregates -* properly set aggregate query sources +- properly set aggregate query sources -* retain parent as bindings +- retain parent as bindings -* don't add `rel_source` at all +- don't add `rel_source` at all -* properly build atoms list +- properly build atoms list -* horribly hack ecto for dynamic bindings +- horribly hack ecto for dynamic bindings -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* update to latest ash +- update to latest ash -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.2](https://github.com/ash-project/ash_postgres/compare/v0.40.1...v0.40.2) (2021-07-02) - - - ### Bug Fixes: -* properly set aggregate query sources +- properly set aggregate query sources -* retain parent as bindings +- retain parent as bindings -* don't add `rel_source` at all +- don't add `rel_source` at all -* properly build atoms list +- properly build atoms list -* horribly hack ecto for dynamic bindings +- horribly hack ecto for dynamic bindings -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* update to latest ash +- update to latest ash -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.1](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc5...v0.40.1) (2021-07-02) - - - ### Bug Fixes: -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* update to latest ash +- update to latest ash -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.0-rc5](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc4...v0.40.0-rc5) (2021-07-01) - - - ### Bug Fixes: -* properly coalesce aggregate values +- properly coalesce aggregate values -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.0-rc4](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc3...v0.40.0-rc4) (2021-06-23) - - - ### Bug Fixes: -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ### Improvements: -* leverage new `private_vars` for errs +- leverage new `private_vars` for errs ## [v0.40.0-rc3](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc2...v0.40.0-rc3) (2021-06-15) - - - ### Bug Fixes: -* always add nullability flag +- always add nullability flag -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ## [v0.40.0-rc2](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc1...v0.40.0-rc2) (2021-06-08) - - - ### Bug Fixes: -* sort references only after other same-table ops +- sort references only after other same-table ops -* generate multitenant foreign keys properly +- generate multitenant foreign keys properly ## [v0.40.0-rc1](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc.0...v0.40.0-rc1) (2021-06-05) - - - ## [v0.39.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.38.11...v0.39.0-rc.0) (2021-06-04) - - - ### Features: -* support expression based calculations +- support expression based calculations -* support concat + if expressions +- support concat + if expressions ### Improvements: -* various other improvements +- various other improvements ## [v0.38.11](https://github.com/ash-project/ash_postgres/compare/v0.38.10...v0.38.11) (2021-05-23) - - - ### Bug Fixes: -* set prefix to "public" for fkeys to public schema +- set prefix to "public" for fkeys to public schema ### Improvements: -* set explicit prefix on join filters +- set explicit prefix on join filters ## [v0.38.10](https://github.com/ash-project/ash_postgres/compare/v0.38.9...v0.38.10) (2021-05-19) - - - ### Improvements: -* support new ash upsert specifying targets +- support new ash upsert specifying targets -* update to latest ash +- update to latest ash ## [v0.38.9](https://github.com/ash-project/ash_postgres/compare/v0.38.8...v0.38.9) (2021-05-12) - - - ### Bug Fixes: -* properly group many_to_many aggregates +- properly group many_to_many aggregates ## [v0.38.8](https://github.com/ash-project/ash_postgres/compare/v0.38.7...v0.38.8) (2021-05-09) - - - ### Improvements: -* update to the latest ash version +- update to the latest ash version ## [v0.38.7](https://github.com/ash-project/ash_postgres/compare/v0.38.6...v0.38.7) (2021-05-09) - - - ### Improvements: -* support latest ash/filtering on related aggregates +- support latest ash/filtering on related aggregates ## [v0.38.6](https://github.com/ash-project/ash_postgres/compare/v0.38.5...v0.38.6) (2021-05-07) - - - ### Bug Fixes: -* properly construct sources for lateral joins +- properly construct sources for lateral joins -* copy the correct data for lateral join queries +- copy the correct data for lateral join queries -* better errors in error cases +- better errors in error cases ### Improvements: -* update to latest ash +- update to latest ash ## [v0.38.5](https://github.com/ash-project/ash_postgres/compare/v0.38.4...v0.38.5) (2021-05-07) - - - ### Bug Fixes: -* don't cast booleans to string in last_ditch_cast +- don't cast booleans to string in last_ditch_cast ## [v0.38.4](https://github.com/ash-project/ash_postgres/compare/v0.38.3...v0.38.4) (2021-05-07) - - - ### Improvements: -* support latest ash version resource sorts +- support latest ash version resource sorts ## [v0.38.3](https://github.com/ash-project/ash_postgres/compare/v0.38.2...v0.38.3) (2021-05-06) - - - ### Improvements: -* update to latest ash +- update to latest ash -* document script to iterate migrations (#65) +- document script to iterate migrations (#65) ## [v0.38.2](https://github.com/ash-project/ash_postgres/compare/v0.38.1...v0.38.2) (2021-05-04) - - - ### Bug Fixes: -* join to join table in lateral join query +- join to join table in lateral join query -* multitenancy + lateral join sources +- multitenancy + lateral join sources -* don't distinct in lateral joins +- don't distinct in lateral joins ## [v0.38.1](https://github.com/ash-project/ash_postgres/compare/v0.38.0...v0.38.1) (2021-05-04) - - - ### Bug Fixes: -* fix fragment processing broken (#64) +- fix fragment processing broken (#64) ## [v0.38.0](https://github.com/ash-project/ash_postgres/compare/v0.37.8...v0.38.0) (2021-04-29) - - - ### Features: -* support new side load improvements +- support new side load improvements ### Improvements: -* Preserve attribute order (#63) +- Preserve attribute order (#63) ## [v0.37.8](https://github.com/ash-project/ash_postgres/compare/v0.37.7...v0.37.8) (2021-04-27) - - - ### Bug Fixes: -* simpler index names +- simpler index names -* don't prefix unique indices with prefix() +- don't prefix unique indices with prefix() -* sort index operations last +- sort index operations last ### Improvements: -* custom index names +- custom index names ## [v0.37.7](https://github.com/ash-project/ash_postgres/compare/v0.37.6...v0.37.7) (2021-04-27) - - - ### Bug Fixes: -* remove inspects that were left in by accident +- remove inspects that were left in by accident ## [v0.37.6](https://github.com/ash-project/ash_postgres/compare/v0.37.5...v0.37.6) (2021-04-27) - - - ### Bug Fixes: -* type cast atoms to strings in last ditch cast +- type cast atoms to strings in last ditch cast -* properly type cast +- properly type cast -* Remove duplicate file extension (#60) +- Remove duplicate file extension (#60) ## [v0.37.5](https://github.com/ash-project/ash_postgres/compare/v0.37.4...v0.37.5) (2021-04-27) - - - ### Bug Fixes: -* properly type cast +- properly type cast ## [v0.37.4](https://github.com/ash-project/ash_postgres/compare/v0.37.3...v0.37.4) (2021-04-26) - - - ### Improvements: -* support `list` aggregate +- support `list` aggregate ## [v0.37.3](https://github.com/ash-project/ash_postgres/compare/v0.37.2...v0.37.3) (2021-04-26) - - - ### Bug Fixes: -* stringify struct defaults in migration generator +- stringify struct defaults in migration generator -* properly comment out extension uninstallation code +- properly comment out extension uninstallation code ## [v0.37.2](https://github.com/ash-project/ash_postgres/compare/v0.37.1...v0.37.2) (2021-04-21) - - - ### Improvements: -* support ash enums +- support ash enums ## [v0.37.1](https://github.com/ash-project/ash_postgres/compare/v0.37.0...v0.37.1) (2021-04-19) - - - ### Bug Fixes: -* include type in references (because it is *not* automatic) +- include type in references (because it is _not_ automatic) ## [v0.37.0](https://github.com/ash-project/ash_postgres/compare/v0.36.5...v0.37.0) (2021-04-19) - - - ### Features: -* add check_constraints, both for validation and migrations +- add check_constraints, both for validation and migrations ## [v0.36.5](https://github.com/ash-project/ash_postgres/compare/v0.36.4...v0.36.5) (2021-04-13) - - - ### Bug Fixes: -* always drop constraints before modifying +- always drop constraints before modifying -* properly compare old references and new references +- properly compare old references and new references ## [v0.36.4](https://github.com/ash-project/ash_postgres/compare/v0.36.3...v0.36.4) (2021-04-12) - - - ### Bug Fixes: -* don't explicitly set type in `references` +- don't explicitly set type in `references` ### Improvements: -* default integers to `:bigint` +- default integers to `:bigint` ## [v0.36.3](https://github.com/ash-project/ash_postgres/compare/v0.36.2...v0.36.3) (2021-04-12) - - - ### Improvements: -* primary autoincrement key as bigserial (#54) +- primary autoincrement key as bigserial (#54) ## [v0.36.2](https://github.com/ash-project/ash_postgres/compare/v0.36.1...v0.36.2) (2021-04-09) - - - ### Improvements: -* support new ash select feature +- support new ash select feature ## [v0.36.1](https://github.com/ash-project/ash_postgres/compare/v0.36.0...v0.36.1) (2021-04-04) - - - ### Bug Fixes: -* raise when `all_tenants/0` default impl is called +- raise when `all_tenants/0` default impl is called ### Improvements: -* add sum aggregate (#53) +- add sum aggregate (#53) ## [v0.36.0](https://github.com/ash-project/ash_postgres/compare/v0.35.5...v0.36.0) (2021-04-01) - - - ### Features: -* support configuring references +- support configuring references -* support configuring polymorphic references +- support configuring polymorphic references -* support `distinct` Ash queries +- support `distinct` Ash queries ## [v0.35.5](https://github.com/ash-project/ash_postgres/compare/v0.35.4...v0.35.5) (2021-03-29) - - - ### Bug Fixes: -* Made AshPostgres.Repo.init/2 overridable (#51) +- Made AshPostgres.Repo.init/2 overridable (#51) ### Improvements: -* only count resources w/ create action for nullability +- only count resources w/ create action for nullability -* better error message on missing table +- better error message on missing table ## [v0.35.4](https://github.com/ash-project/ash_postgres/compare/v0.35.3...v0.35.4) (2021-03-21) - - - ### Bug Fixes: -* reroute `Ash.Type.UUID` to `:uuid` in migrations +- reroute `Ash.Type.UUID` to `:uuid` in migrations -* force create extensions snapshot +- force create extensions snapshot ### Improvements: -* consistent foreign key names +- consistent foreign key names -* support custom foreign key error messages +- support custom foreign key error messages ## [v0.35.3](https://github.com/ash-project/ash_postgres/compare/v0.35.2...v0.35.3) (2021-03-19) - - - ### Bug Fixes: -* force create extensions snapshot +- force create extensions snapshot -* more conservative inner join checks +- more conservative inner join checks -* add back in inner join detection logic +- add back in inner join detection logic ### Improvements: -* consistent foreign key names +- consistent foreign key names -* support custom foreign key error messages +- support custom foreign key error messages ## [v0.35.2](https://github.com/ash-project/ash_postgres/compare/v0.35.1...v0.35.2) (2021-03-05) - - - ### Bug Fixes: -* more conservative inner join checks +- more conservative inner join checks -* add back in inner join detection logic +- add back in inner join detection logic ## [v0.35.1](https://github.com/ash-project/ash_postgres/compare/v0.35.0...v0.35.1) (2021-03-02) - - - ### Bug Fixes: -* don't start the whole app in migrate +- don't start the whole app in migrate ## [v0.35.0](https://github.com/ash-project/ash_postgres/compare/v0.34.7...v0.35.0) (2021-03-02) - - - ### Features: -* automatically install extensions from repo +- automatically install extensions from repo ## [v0.34.7](https://github.com/ash-project/ash_postgres/compare/v0.34.6...v0.34.7) (2021-03-02) - - - ### Bug Fixes: -* typo in references for multitenancy +- typo in references for multitenancy -* `null: true` when attr isn't on all resources for a table +- `null: true` when attr isn't on all resources for a table ## [v0.34.6](https://github.com/ash-project/ash_postgres/compare/v0.34.5...v0.34.6) (2021-02-24) - - - ### Bug Fixes: -* better embedded filters, switch to latest ash +- better embedded filters, switch to latest ash ## [v0.34.5](https://github.com/ash-project/ash_postgres/compare/v0.34.4...v0.34.5) (2021-02-23) - - - ### Improvements: -* support latest ash +- support latest ash ## [v0.34.4](https://github.com/ash-project/ash_postgres/compare/v0.34.3...v0.34.4) (2021-02-08) - - - ### Bug Fixes: -* trim when choosing new attribute name +- trim when choosing new attribute name ## [v0.34.3](https://github.com/ash-project/ash_postgres/compare/v0.34.2...v0.34.3) (2021-02-06) - - - ### Bug Fixes: -* don't reference polymorphic tables to belongs_to relationships +- don't reference polymorphic tables to belongs_to relationships ## [v0.34.2](https://github.com/ash-project/ash_postgres/compare/v0.34.1...v0.34.2) (2021-02-06) - - - ### Bug Fixes: -* set up references properly +- set up references properly ## [v0.34.1](https://github.com/ash-project/ash_postgres/compare/v0.34.0...v0.34.1) (2021-02-06) - - - ### Bug Fixes: -* reference the configured table if set +- reference the configured table if set ## [v0.34.0](https://github.com/ash-project/ash_postgres/compare/v0.33.1...v0.34.0) (2021-02-06) - - - ### Features: -* support polymorphic relationships +- support polymorphic relationships ## [v0.33.1](https://github.com/ash-project/ash_postgres/compare/v0.33.0...v0.33.1) (2021-01-27) - - - ### Bug Fixes: -* actually insert the tracking row +- actually insert the tracking row ## [v0.33.0](https://github.com/ash-project/ash_postgres/compare/v0.32.2...v0.33.0) (2021-01-27) - - - ### Features: -* add `mix ash_postgres.create` +- add `mix ash_postgres.create` -* add `mix ash_postgres.migrate` +- add `mix ash_postgres.migrate` -* add `mix ash_postgres.migrate --tenants` +- add `mix ash_postgres.migrate --tenants` -* add `mix ash_postgres.drop` +- add `mix ash_postgres.drop` ### Bug Fixes: -* rework the way multitenant migrations work +- rework the way multitenant migrations work ## [v0.32.2](https://github.com/ash-project/ash_postgres/compare/v0.32.1...v0.32.2) (2021-01-26) - - - ### Bug Fixes: -* un-break the `in` filter type casting code +- un-break the `in` filter type casting code ### Improvements: -* better errors for multitenant unique constraints +- better errors for multitenant unique constraints ## [v0.32.1](https://github.com/ash-project/ash_postgres/compare/v0.32.0...v0.32.1) (2021-01-24) - - - ### Bug Fixes: -* `ago` was adding, not subtracting +- `ago` was adding, not subtracting ## [v0.32.0](https://github.com/ash-project/ash_postgres/compare/v0.31.1...v0.32.0) (2021-01-24) - - - ### Features: -* support latest ash + contains +- support latest ash + contains ## [v0.31.1](https://github.com/ash-project/ash_postgres/compare/v0.31.0...v0.31.1) (2021-01-22) - - - ### Improvements: -* update to latest ash +- update to latest ash ## [v0.31.0](https://github.com/ash-project/ash_postgres/compare/v0.30.1...v0.31.0) (2021-01-22) - - - ### Features: -* support fragments +- support fragments -* support type casting +- support type casting -* update to latest ash to support expressions +- update to latest ash to support expressions ### Bug Fixes: -* update CI versions +- update CI versions ## [v0.30.1](https://github.com/ash-project/ash_postgres/compare/v0.30.0...v0.30.1) (2021-01-13) - - - ## [v0.30.0](https://github.com/ash-project/ash_postgres/compare/v0.29.6...v0.30.0) (2021-01-13) - - - ### Features: -* Add check_migrated option to migration generator (#40) (#43) +- Add check_migrated option to migration generator (#40) (#43) ## [v0.29.6](https://github.com/ash-project/ash_postgres/compare/v0.29.5...v0.29.6) (2021-01-12) - - - ### Bug Fixes: -* rename out of phase, small migration fix +- rename out of phase, small migration fix ## [v0.29.5](https://github.com/ash-project/ash_postgres/compare/v0.29.4...v0.29.5) (2021-01-10) - - - ### Improvements: -* Use ecto_sql formatter settings (#38) +- Use ecto_sql formatter settings (#38) ## [v0.29.4](https://github.com/ash-project/ash_postgres/compare/v0.29.3...v0.29.4) (2021-01-10) - - - ### Improvements: -* Omit field opts if they are default values (#37) +- Omit field opts if they are default values (#37) ## [v0.29.3](https://github.com/ash-project/ash_postgres/compare/v0.29.2...v0.29.3) (2021-01-08) - - - ### Improvements: -* support latest ash +- support latest ash ## [v0.29.2](https://github.com/ash-project/ash_postgres/compare/v0.29.1...v0.29.2) (2021-01-08) - - - ### Improvements: -* Make integer serial if generated +- Make integer serial if generated ## [v0.29.1](https://github.com/ash-project/ash_postgres/compare/v0.29.0...v0.29.1) (2021-01-08) - - - ### Improvements: -* support latest ash version +- support latest ash version ## [v0.29.0](https://github.com/ash-project/ash_postgres/compare/v0.28.1...v0.29.0) (2021-01-08) - - - ### Features: -* retain snapshot history +- retain snapshot history ### Improvements: -* support latest ash version +- support latest ash version ## [v0.28.1](https://github.com/ash-project/ash_postgres/compare/v0.28.0...v0.28.1) (2021-01-07) - - - ### Improvements: -* Add :binary migration type (#33) +- Add :binary migration type (#33) ## [v0.28.0](https://github.com/ash-project/ash_postgres/compare/v0.27.0...v0.28.0) (2020-12-29) - - - ### Features: -* support latest Ash version +- support latest Ash version ## [v0.27.0](https://github.com/ash-project/ash_postgres/compare/v0.26.2...v0.27.0) (2020-12-23) - - - ### Features: -* support refs on both sides of operators +- support refs on both sides of operators ### Bug Fixes: -* bump ash version +- bump ash version ## [v0.26.2](https://github.com/ash-project/ash_postgres/compare/v0.26.1...v0.26.2) (2020-12-06) - - - ### Bug Fixes: -* properly accept the `tenant_migration_path` +- properly accept the `tenant_migration_path` ## [v0.26.1](https://github.com/ash-project/ash_postgres/compare/v0.26.0...v0.26.1) (2020-12-01) - - - ### Bug Fixes: -* set default properly when modifying +- set default properly when modifying ## [v0.26.0](https://github.com/ash-project/ash_postgres/compare/v0.25.5...v0.26.0) (2020-11-25) - - - ### Features: -* don't drop columns unless explicitly told to +- don't drop columns unless explicitly told to ### Bug Fixes: -* various migration generator bug fixes +- various migration generator bug fixes ## [v0.25.5](https://github.com/ash-project/ash_postgres/compare/v0.25.4...v0.25.5) (2020-11-17) - - - ### Bug Fixes: -* drop constraints outside of phases (#29) +- drop constraints outside of phases (#29) ## [v0.25.4](https://github.com/ash-project/ash_postgres/compare/v0.25.3...v0.25.4) (2020-11-07) - - - ### Bug Fixes: -* only alter the things that have changed +- only alter the things that have changed ## [v0.25.3](https://github.com/ash-project/ash_postgres/compare/v0.25.2...v0.25.3) (2020-11-06) - - - ### Improvements: -* add utc_datetime migration type +- add utc_datetime migration type ## [v0.25.2](https://github.com/ash-project/ash_postgres/compare/v0.25.1...v0.25.2) (2020-11-03) - - - ### Bug Fixes: -* access data_layer_query with function +- access data_layer_query with function ## [v0.25.1](https://github.com/ash-project/ash_postgres/compare/v0.25.0...v0.25.1) (2020-10-29) - - - ### Improvements: -* mark repo as not requiring compile-time dep +- mark repo as not requiring compile-time dep ## [v0.25.0](https://github.com/ash-project/ash_postgres/compare/v0.24.0...v0.25.0) (2020-10-29) - - - ### Features: -* multitenancy (#25) +- multitenancy (#25) ### Bug Fixes: -* verify repo using ensure_compiled +- verify repo using ensure_compiled ## [v0.24.0](https://github.com/ash-project/ash_postgres/compare/v0.23.2...v0.24.0) (2020-10-17) - - - ### Features: -* support latest ash +- support latest ash ## [v0.23.2](https://github.com/ash-project/ash_postgres/compare/v0.23.1...v0.23.2) (2020-10-07) - - - ## [v0.23.1](https://github.com/ash-project/ash_postgres/compare/v0.23.0...v0.23.1) (2020-10-06) - - - ## [v0.23.0](https://github.com/ash-project/ash_postgres/compare/v0.22.1...v0.23.0) (2020-10-06) - - - ### Features: -* update to latest ash, trigram filter +- update to latest ash, trigram filter ## [v0.22.1](https://github.com/ash-project/ash_postgres/compare/v0.22.0...v0.22.1) (2020-10-01) - - - ### Bug Fixes: -* don't group alters with creates (#22) +- don't group alters with creates (#22) -* add jason dependency, clean lockfile (#21) +- add jason dependency, clean lockfile (#21) ## [v0.22.0](https://github.com/ash-project/ash_postgres/compare/v0.21.0...v0.22.0) (2020-09-24) - - - ### Features: -* fix error when filtering with `true` +- fix error when filtering with `true` ### Bug Fixes: -* broken types for `in` operator +- broken types for `in` operator ## [v0.21.0](https://github.com/ash-project/ash_postgres/compare/v0.20.1...v0.21.0) (2020-09-19) - - - ### Features: -* support base_filter (#18) +- support base_filter (#18) ## [v0.20.1](https://github.com/ash-project/ash_postgres/compare/v0.20.0...v0.20.1) (2020-09-11) - - - ### Bug Fixes: -* document/update migration path logic +- document/update migration path logic ## [v0.20.0](https://github.com/ash-project/ash_postgres/compare/v0.19.0...v0.20.0) (2020-09-11) - - - ### Features: -* snapshot-based migration generator +- snapshot-based migration generator ## [v0.19.0](https://github.com/ash-project/ash_postgres/compare/v0.18.0...v0.19.0) (2020-09-02) diff --git a/README.md b/README.md index 61ae04f4..3999424d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Then, configure each of your `Ash.Resource` resources by adding `use Ash.Resourc ```elixir defmodule MyApp.SomeResource do - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, domain: MyDomain, data_layer: AshPostgres.DataLayer postgres do repo MyApp.Repo diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs index e8f20296..5d9ee395 100644 --- a/benchmarks/bulk_create.exs +++ b/benchmarks/bulk_create.exs @@ -1,4 +1,4 @@ -alias AshPostgres.Test.{Api, Post} +alias AshPostgres.Test.{Domain, Post} ten_rows = 1..10 @@ -25,7 +25,7 @@ hundred_thousand_rows = end) # do them both once to warm things up -Api.bulk_create(ten_rows, Post, :create, +Ash.bulk_create(ten_rows, Post, :create, batch_size: 10, max_concurrency: 2 ) @@ -39,13 +39,13 @@ batch_size = 200 Benchee.run( %{ "ash sync": fn input -> - %{error_count: 0} = Api.bulk_create(input, Post, :create, + %{error_count: 0} = Ash.bulk_create(input, Post, :create, batch_size: batch_size, transaction: false ) end, "ash sync assuming casted": fn input -> - %{error_count: 0} = Api.bulk_create(input, Post, :create, + %{error_count: 0} = Ash.bulk_create(input, Post, :create, batch_size: batch_size, transaction: false, assume_casted?: true @@ -62,7 +62,7 @@ Benchee.run( input |> Stream.chunk_every(batch_size) |> Task.async_stream(fn batch -> - %{error_count: 0} = Api.bulk_create(batch, Post, :create, + %{error_count: 0} = Ash.bulk_create(batch, Post, :create, transaction: false ) end, max_concurrency: max_concurrency, timeout: :infinity) @@ -72,7 +72,7 @@ Benchee.run( input |> Stream.chunk_every(batch_size) |> Task.async_stream(fn batch -> - %{error_count: 0} = Api.bulk_create(batch, Post, :create, + %{error_count: 0} = Ash.bulk_create(batch, Post, :create, transaction: false, assume_casted?: true ) @@ -80,14 +80,14 @@ Benchee.run( |> Stream.run() end, "ash using own async option": fn input -> - %{error_count: 0} = Api.bulk_create(input, Post, :create, + %{error_count: 0} = Ash.bulk_create(input, Post, :create, transaction: false, max_concurrency: max_concurrency, batch_size: batch_size ) end, "ash using own async option assuming casted": fn input -> - %{error_count: 0} = Api.bulk_create(input, Post, :create, + %{error_count: 0} = Ash.bulk_create(input, Post, :create, transaction: false, assume_casted?: true, max_concurrency: max_concurrency, diff --git a/config/config.exs b/config/config.exs index 0b18acfa..f3c392c6 100644 --- a/config/config.exs +++ b/config/config.exs @@ -15,8 +15,8 @@ if Mix.env() == :dev do end if Mix.env() == :test do - config :ash, :validate_api_resource_inclusion?, false - config :ash, :validate_api_config_inclusion?, false + config :ash, :validate_domain_resource_inclusion?, false + config :ash, :validate_domain_config_inclusion?, false config :ash_postgres, AshPostgres.TestRepo, username: "postgres", @@ -42,10 +42,10 @@ if Mix.env() == :test do config :ash_postgres, ecto_repos: [AshPostgres.TestRepo, AshPostgres.TestNoSandboxRepo], - ash_apis: [ - AshPostgres.Test.Api, - AshPostgres.MultitenancyTest.Api, - AshPostgres.Test.ComplexCalculations.Api + ash_domains: [ + AshPostgres.Test.Domain, + AshPostgres.MultitenancyTest.Domain, + AshPostgres.Test.ComplexCalculations.Domain ] config :logger, level: :warning diff --git a/documentation/how_to/join-manual-relationships.md b/documentation/how_to/join-manual-relationships.md index f53fa0ed..76b577de 100644 --- a/documentation/how_to/join-manual-relationships.md +++ b/documentation/how_to/join-manual-relationships.md @@ -38,7 +38,7 @@ defmodule Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold do |> Enum.group_by(& &1.representative_id)} end - # query is the "source" query that is being built. + # query is the "source" query that is being built. # _opts are options provided to the manual relationship, i.e `{Manual, opt: :val}` diff --git a/documentation/topics/migrations_and_tasks.md b/documentation/topics/migrations_and_tasks.md index 6afdace8..694a2dd4 100644 --- a/documentation/topics/migrations_and_tasks.md +++ b/documentation/topics/migrations_and_tasks.md @@ -25,7 +25,7 @@ AshPostgres is built on top of ecto, so much of its behavior is pass-through/orc For more information on generating migrations, see the module documentation here: `Mix.Tasks.AshPostgres.GenerateMigrations`, or run `mix help ash_postgres.generate_migrations` -For running your migrations, there is a mix task that will find all of the repos configured in your apis and run their +For running your migrations, there is a mix task that will find all of the repos configured in your domains and run their migrations. It is a thin wrapper around `mix ecto.migrate`. Ours is called `mix ash_postgres.migrate` If you want to run or rollback individual migrations, use the corresponding @@ -146,17 +146,17 @@ Tasks that need to be executed in the released application (because mix is not p end defp repos do - apis() - |> Enum.flat_map(fn api -> - api - |> Ash.Api.Info.resources() + domains() + |> Enum.flat_map(fn domain -> + domain + |> Ash.Domain.Info.resources() |> Enum.map(&AshPostgres.DataLayer.Info.repo/1) end) |> Enum.uniq() end - defp apis do - Application.fetch_env!(@app, :ash_apis) + defp domains do + Application.fetch_env!(@app, :ash_domains) end defp load_app do diff --git a/documentation/topics/polymorphic_resources.md b/documentation/topics/polymorphic_resources.md index fdab27a1..038f1cd8 100644 --- a/documentation/topics/polymorphic_resources.md +++ b/documentation/topics/polymorphic_resources.md @@ -5,6 +5,7 @@ To support leveraging the same resource backed by multiple tables (useful for th ```elixir defmodule MyApp.Reaction do use Ash.Resource, + domain: MyDomain, data_layer: AshPostgres.DataLayer postgres do @@ -12,9 +13,9 @@ defmodule MyApp.Reaction do end attributes do - attribute(:resource_id, :uuid) + attribute :resource_id, :uuid, public?: true end - + ... end ``` @@ -24,6 +25,7 @@ Then, in your related resources, you set the table context like so: ```elixir defmodule MyApp.Post do use Ash.Resource, + domain: MyDomain, data_layer: AshPostgres.DataLayer ... @@ -37,6 +39,7 @@ end defmodule MyApp.Comment do use Ash.Resource, + domain: MyDomain, data_layer: AshPostgres.DataLayer ... @@ -61,6 +64,7 @@ For example: ```elixir defmodule MyApp.Reaction do + # ... actions do read :for_comments do prepare set_context(%{data_layer: %{table: "comment_reactions"}}) @@ -78,5 +82,5 @@ end When a migration is marked as `polymorphic? true`, the migration generator will look at all resources that are related to it, that set the `%{data_layer: %{table: "table"}}` context. For each of those, a migration is generated/managed automatically. This means that adding reactions -to a new resource is as easy as adding the relationship and table context, and then running +to a new resource is as easy as adding the relationship and table context, and then running `mix ash_postgres.generate_migrations`. diff --git a/documentation/tutorials/get-started-with-postgres.md b/documentation/tutorials/get-started-with-postgres.md index 5a6bfa5e..13e2fccd 100644 --- a/documentation/tutorials/get-started-with-postgres.md +++ b/documentation/tutorials/get-started-with-postgres.md @@ -68,7 +68,7 @@ import Config # This should already have been added in the first # getting started guide config :helpdesk, - ash_apis: [Helpdesk.Support] + ash_domains: [Helpdesk.Support] config :helpdesk, ecto_repos: [Helpdesk.Repo] @@ -155,6 +155,7 @@ Now we can add the data layer to our resources. The basic configuration for a re # in lib/helpdesk/support/resources/ticket.ex use Ash.Resource, + domain: Helpdesk.Support, data_layer: AshPostgres.DataLayer postgres do @@ -167,6 +168,7 @@ Now we can add the data layer to our resources. The basic configuration for a re # in lib/helpdesk/support/resources/representative.ex use Ash.Resource, + domain: Helpdesk.Support, data_layer: AshPostgres.DataLayer postgres do diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 581ee86e..84a4cf70 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -499,8 +499,7 @@ defmodule AshPostgres.Aggregate do end defp resource_aggregates_to_aggregates(resource, aggregates) do - aggregates - |> Enum.reduce_while({:ok, []}, fn + Enum.reduce_while(aggregates, {:ok, []}, fn %Ash.Query.Aggregate{} = aggregate, {:ok, aggregates} -> {:cont, {:ok, [aggregate | aggregates]}} @@ -523,6 +522,8 @@ defmodule AshPostgres.Aggregate do default: aggregate.default, filterable?: aggregate.filterable?, type: aggregate.type, + sortable?: aggregate.filterable?, + include_nil?: aggregate.include_nil?, constraints: aggregate.constraints, implementation: aggregate.implementation, uniq?: aggregate.uniq?, @@ -685,11 +686,11 @@ defmodule AshPostgres.Aggregate do type: type, constraints: constraints } -> - {:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, {type, constraints}) + {:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, type, constraints) expression = module.expression(opts, aggregate.context) expression = - Ash.Filter.build_filter_from_template( + Ash.Expr.fill_template( expression, aggregate.context[:actor], aggregate.context, @@ -719,11 +720,11 @@ defmodule AshPostgres.Aggregate do expression = module.expression(opts, aggregate.context) expression = - Ash.Filter.build_filter_from_template( + Ash.Expr.fill_template( expression, - aggregate.context[:actor], - aggregate.context, - aggregate.context + aggregate.context.actor, + aggregate.context.arguments, + aggregate.context.source_context ) {:ok, expression} = @@ -1189,6 +1190,13 @@ defmodule AshPostgres.Aggregate do has_sort? = has_sort?(aggregate.query) + array_agg = + if AshPostgres.DataLayer.Info.pg_version_matches?(aggregate.resource, ">= 16.0.0") do + "any_value" + else + "array_agg" + end + {sorted, query} = if has_sort? || first_relationship.sort not in [nil, []] do {sort, binding} = @@ -1211,31 +1219,63 @@ defmodule AshPostgres.Aggregate do :return ) - question_marks = Enum.map(sort_expr, fn _ -> " ? " end) + if aggregate.include_nil? do + question_marks = Enum.map(sort_expr, fn _ -> " ? " end) - {:ok, expr} = - AshPostgres.Functions.Fragment.casted_new( - ["array_agg(? ORDER BY #{question_marks})", field] ++ sort_expr - ) + {:ok, expr} = + Ash.Query.Function.Fragment.casted_new( + ["#{array_agg}(? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))", field] ++ + sort_expr ++ [field] + ) - {sort_expr, acc} = - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) + {sort_expr, acc} = + AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) - query = - AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + query = + AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + + {sort_expr, query} + else + question_marks = Enum.map(sort_expr, fn _ -> " ? " end) - {sort_expr, query} + {:ok, expr} = + Ash.Query.Function.Fragment.casted_new( + ["#{array_agg}(? ORDER BY #{question_marks})", field] ++ sort_expr + ) + + {sort_expr, acc} = + AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) + + query = + AshPostgres.DataLayer.merge_expr_accumulator(query, acc) + + {sort_expr, query} + end else - {Ecto.Query.dynamic( - [row], - fragment("array_agg(?)", ^field) - ), query} + case array_agg do + "array_agg" -> + {Ecto.Query.dynamic( + [row], + fragment("array_agg(?)", ^field) + ), query} + + "any_value" -> + {Ecto.Query.dynamic( + [row], + fragment("any_value(?)", ^field) + ), query} + end end {query, filtered} = filter_field(sorted, query, aggregate, relationship_path, is_single?) - value = Ecto.Query.dynamic(fragment("(?)[1]", ^filtered)) + value = + if array_agg == "array_agg" do + Ecto.Query.dynamic(fragment("(?)[1]", ^filtered)) + else + filtered + end with_default = if aggregate.default_value do @@ -1327,10 +1367,26 @@ defmodule AshPostgres.Aggregate do "" end - {:ok, expr} = - AshPostgres.Functions.Fragment.casted_new( - ["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr - ) + expr = + if aggregate.include_nil? do + {:ok, expr} = + Ash.Query.Function.Fragment.casted_new( + [ + "array_agg(#{distinct}? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))", + field + ] ++ + sort_expr ++ [field] + ) + + expr + else + {:ok, expr} = + Ash.Query.Function.Fragment.casted_new( + ["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr + ) + + expr + end {expr, acc} = AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) diff --git a/lib/calculation.ex b/lib/calculation.ex index f896698d..1e030287 100644 --- a/lib/calculation.ex +++ b/lib/calculation.ex @@ -73,10 +73,11 @@ defmodule AshPostgres.Calculation do expression = Ash.Actions.Read.add_calc_context_to_filter( expression, - calculation.context[:actor], - calculation.context[:authorize?], - calculation.context[:tenant], - calculation.context[:tracer] + calculation.context.actor, + calculation.context.authorize?, + calculation.context.tenant, + calculation.context.tracer, + nil ) {expr, acc} = diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 995bc231..fec27840 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -384,11 +384,12 @@ defmodule AshPostgres.DataLayer do use Spark.Dsl.Extension, sections: @sections, - transformers: [ - AshPostgres.Transformers.ValidateReferences, - AshPostgres.Transformers.EnsureTableOrPolymorphic, - AshPostgres.Transformers.PreventMultidimensionalArrayAggregates, - AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchType + verifiers: [ + AshPostgres.Verifiers.VerifyPostgresVersion, + AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates, + AshPostgres.Verifiers.ValidateReferences, + AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType, + AshPostgres.Verifiers.EnsureTableOrPolymorphic ] def migrate(args) do @@ -704,7 +705,7 @@ defmodule AshPostgres.DataLayer do repo = dynamic_repo(resource, query) with_savepoint(repo, query, fn -> - {:ok, repo.all(query, repo_opts(nil, nil, resource)) |> remap_mapped_fields(query)} + {:ok, repo.all(query, repo_opts(repo, nil, nil, resource)) |> remap_mapped_fields(query)} end) end rescue @@ -715,7 +716,7 @@ defmodule AshPostgres.DataLayer do defp no_table?(%{from: %{source: {"", _}}}), do: true defp no_table?(_), do: false - defp repo_opts(timeout, nil, resource) do + defp repo_opts(_repo, timeout, nil, resource) do if schema = AshPostgres.DataLayer.Info.schema(resource) do [prefix: schema] else @@ -724,9 +725,9 @@ defmodule AshPostgres.DataLayer do |> add_timeout(timeout) end - defp repo_opts(timeout, tenant, resource) do + defp repo_opts(_repo, timeout, tenant, resource) do if Ash.Resource.Info.multitenancy_strategy(resource) == :context do - [prefix: tenant] + [prefix: Ash.ToTenant.to_tenant(resource, tenant)] else if schema = AshPostgres.DataLayer.Info.schema(resource) do [prefix: schema] @@ -748,7 +749,6 @@ defmodule AshPostgres.DataLayer do config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() functions = [ - AshPostgres.Functions.Fragment, AshPostgres.Functions.Like, AshPostgres.Functions.ILike ] @@ -844,7 +844,8 @@ defmodule AshPostgres.DataLayer do %{} _ -> - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + repo = dynamic_repo(resource, query) + repo.one(query, repo_opts(repo, nil, nil, resource)) end {:ok, add_single_aggs(result, resource, original_query, cant_group)} @@ -880,10 +881,12 @@ defmodule AshPostgres.DataLayer do |> Ecto.Query.select(%{}) end + repo = dynamic_repo(resource, filtered) + Map.put( result || %{}, agg.name, - dynamic_repo(resource, filtered).exists?(filtered, repo_opts(nil, nil, resource)) + repo.exists?(filtered, repo_opts(repo, nil, nil, resource)) ) agg, result -> @@ -953,9 +956,11 @@ defmodule AshPostgres.DataLayer do first_relationship ) + repo = dynamic_repo(resource, query) + Map.merge( result || %{}, - dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) + repo.one(query, repo_opts(repo, nil, nil, resource)) ) end) end @@ -1049,9 +1054,11 @@ defmodule AshPostgres.DataLayer do %{} _ -> - dynamic_repo(source_resource, query).one( + repo = dynamic_repo(source_resource, query) + + repo.one( query, - repo_opts(nil, nil, source_resource) + repo_opts(repo, nil, nil, source_resource) ) end @@ -1082,10 +1089,12 @@ defmodule AshPostgres.DataLayer do |> elem(0) |> Map.get(:resource) + repo = dynamic_repo(source_resource, lateral_join_query) + results = - dynamic_repo(source_resource, lateral_join_query).all( + repo.all( lateral_join_query, - repo_opts(nil, nil, source_resource) + repo_opts(repo, nil, nil, source_resource) ) |> remap_mapped_fields(query) @@ -1357,7 +1366,8 @@ defmodule AshPostgres.DataLayer do @doc false def set_subquery_prefix(data_layer_query, source_query, resource) do - config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() + repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) + config = repo.config() case data_layer_query do %{__ash_bindings__: %{context: %{data_layer: %{schema: schema}}}} when not is_nil(schema) -> @@ -1375,20 +1385,16 @@ defmodule AshPostgres.DataLayer do %{ data_layer_query | prefix: - to_string( - query_tenant || AshPostgres.DataLayer.Info.schema(resource) || - config[:default_prefix] || - "public" - ) + query_tenant || AshPostgres.DataLayer.Info.schema(resource) || + config[:default_prefix] || + "public" } else %{ data_layer_query | prefix: - to_string( - AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || - "public" - ) + AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || + "public" } end end @@ -1418,7 +1424,7 @@ defmodule AshPostgres.DataLayer do data end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :update, true, true) + |> ecto_changeset(changeset, :update, true) case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do {:error, error} -> @@ -1427,12 +1433,12 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> try do repo = dynamic_repo(resource, changeset) - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) case query_with_atomics( resource, query, - ecto_changeset.filters, + changeset.filter, changeset.atomics, ecto_changeset.changes, [] @@ -1582,7 +1588,7 @@ defmodule AshPostgres.DataLayer do data end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :update, true, true) + |> ecto_changeset(changeset, :update, true) try do query = @@ -1599,10 +1605,10 @@ defmodule AshPostgres.DataLayer do end |> Ecto.Query.exclude(:order_by) - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - repo = dynamic_repo(resource, changeset) + repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) + query = if Enum.any?(query.joins, &(&1.qual != :inner)) do root_query = @@ -1663,7 +1669,11 @@ defmodule AshPostgres.DataLayer do @impl true def bulk_create(resource, stream, options) do - opts = repo_opts(nil, options[:tenant], resource) + changesets = Enum.to_list(stream) + + repo = dynamic_repo(resource, Enum.at(changesets, 0)) + + opts = repo_opts(repo, nil, options[:tenant], resource) opts = if options.return_records? do @@ -1672,8 +1682,6 @@ defmodule AshPostgres.DataLayer do opts end - changesets = Enum.to_list(stream) - repo = dynamic_repo(resource, Enum.at(changesets, 0)) source = resolve_source(resource, Enum.at(changesets, 0)) try do @@ -1681,7 +1689,7 @@ defmodule AshPostgres.DataLayer do if options[:upsert?] do # Ash groups changesets by atomics before dispatching them to the data layer # this means that all changesets have the same atomics - %{atomics: atomics, filters: filters} = Enum.at(changesets, 0) + %{atomics: atomics, filter: filter} = Enum.at(changesets, 0) query = from(row in source, as: ^0) @@ -1696,7 +1704,7 @@ defmodule AshPostgres.DataLayer do case query_with_atomics( resource, query, - filters, + filter, atomics, %{}, upsert_set @@ -1967,10 +1975,6 @@ defmodule AshPostgres.DataLayer do end) end - defp handle_errors({:error, %Ecto.Changeset{errors: errors}}) do - {:error, Enum.map(errors, &to_ash_error/1)} - end - defp to_ash_error({field, {message, vars}}) do Ash.Error.Changes.InvalidAttribute.exception( field: field, @@ -1979,25 +1983,7 @@ defmodule AshPostgres.DataLayer do ) end - defp ecto_changeset(record, changeset, type, table_error? \\ true, bulk_update? \\ false) do - filters = - if changeset.action_type == :create do - %{} - else - Map.get(changeset, :filters, %{}) - end - - filters = - if changeset.action_type == :create || bulk_update? do - filters - else - changeset.resource - |> Ash.Resource.Info.primary_key() - |> Enum.reduce(filters, fn key, filters -> - Map.put(filters, key, Map.get(record, key)) - end) - end - + defp ecto_changeset(record, changeset, type, table_error? \\ true) do attributes = changeset.resource |> Ash.Resource.Info.attributes() @@ -2013,7 +1999,6 @@ defmodule AshPostgres.DataLayer do |> to_ecto() |> set_table(changeset, type, table_error?) |> Ecto.Changeset.change(Map.take(changeset.attributes, attributes_to_change)) - |> Map.update!(:filters, &Map.merge(&1, filters)) |> add_configured_foreign_key_constraints(record.__struct__) |> add_unique_indexes(record.__struct__, changeset) |> add_check_constraints(record.__struct__) @@ -2615,7 +2600,11 @@ defmodule AshPostgres.DataLayer do |> ecto_changeset(changeset, :update) source = resolve_source(resource, changeset) - query = from(row in source, as: ^0) |> default_bindings(resource, changeset.context) + + query = + from(row in source, as: ^0) + |> default_bindings(resource, changeset.context) + |> pkey_filter(changeset.data) select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) @@ -2630,7 +2619,7 @@ defmodule AshPostgres.DataLayer do case query_with_atomics( resource, query, - ecto_changeset.filters, + changeset.filter, changeset.atomics, ecto_changeset.changes, [] @@ -2639,13 +2628,12 @@ defmodule AshPostgres.DataLayer do {:ok, changeset.data} {:ok, query} -> - repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) + repo = dynamic_repo(resource, changeset) + repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) repo_opts = Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - repo = dynamic_repo(resource, changeset) - result = with_savepoint(repo, query, fn -> repo.update_all( @@ -2660,7 +2648,7 @@ defmodule AshPostgres.DataLayer do {:error, Ash.Error.Changes.StaleRecord.exception( resource: resource, - filters: ecto_changeset.filters + filters: changeset.filter )} {1, [result]} -> @@ -2684,20 +2672,29 @@ defmodule AshPostgres.DataLayer do end end + defp pkey_filter(query, %resource{} = record) do + pkey = + record + |> Map.take(Ash.Resource.Info.primary_key(resource)) + |> Map.to_list() + + Ecto.Query.where(query, ^pkey) + end + defp query_with_atomics( resource, query, - filters, + filter, atomics, updating_one_changes, existing_set ) do query = - Enum.reduce(filters, query, fn {key, value}, query -> - from(row in query, - where: field(row, ^key) == ^value - ) - end) + if is_nil(filter) do + query + else + filter(query, filter, resource) + end atomics_result = Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> @@ -2792,20 +2789,23 @@ defmodule AshPostgres.DataLayer do try do repo = dynamic_repo(resource, changeset) - result = - repo.delete( - ecto_changeset, - repo_opts(changeset.timeout, changeset.tenant, changeset.resource) - ) + source = resolve_source(resource, changeset) - result - |> from_ecto() + from(row in source, as: ^0) + |> default_bindings(resource, changeset.context) + |> filter(changeset.filter, resource) |> case do - {:ok, _record} -> + {:ok, query} -> + query + |> pkey_filter(record) + |> repo.delete_all( + repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) + ) + :ok {:error, error} -> - handle_errors({:error, error}) + {:error, error} end rescue e -> @@ -3201,9 +3201,13 @@ defmodule AshPostgres.DataLayer do |> Enum.reduce(query, fn filter, query -> {dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__) - query - |> Ecto.Query.where(^dynamic) - |> merge_expr_accumulator(acc) + if is_nil(dynamic) do + query + else + query + |> Ecto.Query.where([], ^dynamic) + |> merge_expr_accumulator(acc) + end end) end diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 3184660b..8126ce1e 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -14,6 +14,21 @@ defmodule AshPostgres.DataLayer.Info do end end + @doc "Checks a version requirement against the resource's repo's postgres version" + def pg_version_matches?(resource, requirement) do + resource + |> pg_version() + |> Version.match?(requirement) + end + + @doc "Gets the resource's repo's postgres version" + def pg_version(resource) do + case repo(resource, :read).pg_version() do + %Version{} = version -> version + string when is_binary(string) -> Version.parse!(string) + end + end + @doc "The configured table for a resource" def table(resource) do Extension.get_opt(resource, [:postgres], :table, nil, true) diff --git a/lib/expr.ex b/lib/expr.ex index 8cd6b87a..305f9e9d 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -14,6 +14,7 @@ defmodule AshPostgres.Expr do DateAdd, DateTimeAdd, Error, + Fragment, FromNow, GetPath, If, @@ -21,6 +22,7 @@ defmodule AshPostgres.Expr do Length, Now, Round, + StringDowncase, StringJoin, StringLength, StringSplit, @@ -29,7 +31,7 @@ defmodule AshPostgres.Expr do Type } - alias AshPostgres.Functions.{Fragment, ILike, Like, TrigramSimilarity, VectorCosineDistance} + alias AshPostgres.Functions.{ILike, Like, TrigramSimilarity, VectorCosineDistance} require Ecto.Query @@ -725,6 +727,27 @@ defmodule AshPostgres.Expr do ) end + defp do_dynamic_expr( + query, + %StringDowncase{arguments: [value], embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + type + ) do + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [raw: "lower(", expr: value, raw: ")"] + }, + bindings, + embedded?, + acc, + type + ) + end + defp do_dynamic_expr( query, %StringTrim{arguments: [value], embedded?: pred_embedded?}, @@ -1074,10 +1097,11 @@ defmodule AshPostgres.Expr do expression = Ash.Actions.Read.add_calc_context_to_filter( expression, - calculation.context[:actor], - calculation.context[:authorize?], - calculation.context[:tenant], - calculation.context[:tracer] + calculation.context.actor, + calculation.context.authorize?, + calculation.context.tenant, + calculation.context.tracer, + nil ) do_dynamic_expr( @@ -1190,7 +1214,8 @@ defmodule AshPostgres.Expr do aggregate.context[:actor], aggregate.context[:authorize?], aggregate.context[:tenant], - aggregate.context[:tracer] + aggregate.context[:tracer], + nil ) {value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc) @@ -1452,10 +1477,10 @@ defmodule AshPostgres.Expr do end {encoded, acc} = - if Ash.Filter.TemplateHelpers.expr?(input) do + if Ash.Expr.expr?(input) do frag_parts = Enum.flat_map(input, fn {key, value} -> - if Ash.Filter.TemplateHelpers.expr?(value) do + if Ash.Expr.expr?(value) do [ expr: to_string(key), raw: "::text, ", @@ -1720,7 +1745,7 @@ defmodule AshPostgres.Expr do when is_map(value) and not is_struct(value) do if bindings[:location] == :update && Enum.any?(value, fn {key, value} -> - Ash.Filter.TemplateHelpers.expr?(key) || Ash.Filter.TemplateHelpers.expr?(value) + Ash.Expr.expr?(key) || Ash.Expr.expr?(value) end) do elements = value @@ -1770,7 +1795,7 @@ defmodule AshPostgres.Expr do if other && is_atom(other) && !is_boolean(other) do {to_string(other), acc} else - if Ash.Filter.TemplateHelpers.expr?(other) do + if Ash.Expr.expr?(other) do if is_list(other) do list_expr(query, other, bindings, true, acc, type) else @@ -1801,7 +1826,7 @@ defmodule AshPostgres.Expr do end defp do_dynamic_expr(query, value, bindings, false, acc, type) do - if Ash.Filter.TemplateHelpers.expr?(value) do + if Ash.Expr.expr?(value) do if is_list(value) do list_expr(query, value, bindings, false, acc, type) else @@ -2033,7 +2058,7 @@ defmodule AshPostgres.Expr do defp list_expr(query, value, bindings, embedded?, acc, type) do if !Enum.empty?(value) && Enum.any?(value, fn value -> - Ash.Filter.TemplateHelpers.expr?(value) || is_map(value) || is_list(value) + Ash.Expr.expr?(value) || is_map(value) || is_list(value) end) do type = case type do diff --git a/lib/functions/fragment.ex b/lib/functions/fragment.ex deleted file mode 100644 index 84387e8e..00000000 --- a/lib/functions/fragment.ex +++ /dev/null @@ -1,72 +0,0 @@ -defmodule AshPostgres.Functions.Fragment do - @moduledoc """ - A function that maps to ecto's `fragment` function - - https://hexdocs.pm/ecto/Ecto.Query.API.html#fragment/1 - """ - - use Ash.Query.Function, name: :fragment - - def private?, do: true - - # Varargs is special, and should only be used in rare circumstances (like this one) - # no type casting or help can be provided for these functions. - def args, do: :var_args - - def new([fragment | _]) when not is_binary(fragment) do - {:error, "First argument to `fragment` must be a string."} - end - - def new([fragment | rest]) do - split = split_fragment(fragment) - - if Enum.count(split, &(&1 == :slot)) != length(rest) do - {:error, - "fragment(...) expects extra arguments in the same amount of question marks in string. " <> - "It received #{Enum.count(split, &(&1 == :slot))} extra argument(s) but expected #{length(rest)}"} - else - {:ok, %__MODULE__{arguments: merge_fragment(split, rest)}} - end - end - - def casted_new([fragment | _]) when not is_binary(fragment) do - {:error, "First argument to `fragment` must be a string."} - end - - def casted_new([fragment | rest]) do - split = split_fragment(fragment) - - if Enum.count(split, &(&1 == :slot)) != length(rest) do - {:error, - "fragment(...) expects extra arguments in the same amount of question marks in string. " <> - "It received #{Enum.count(split, &(&1 == :slot))} extra argument(s) but expected #{length(rest)}"} - else - {:ok, %__MODULE__{arguments: merge_fragment(split, rest, :casted_expr)}} - end - end - - defp merge_fragment(expr, args, tag \\ :expr) - defp merge_fragment([], [], _tag), do: [] - - defp merge_fragment([:slot | rest], [arg | rest_args], tag) do - [{tag, arg} | merge_fragment(rest, rest_args, tag)] - end - - defp merge_fragment([val | rest], rest_args, tag) do - [{:raw, val} | merge_fragment(rest, rest_args, tag)] - end - - defp split_fragment(frag, consumed \\ "") - - defp split_fragment(<<>>, consumed), - do: [consumed] - - defp split_fragment(<>, consumed), - do: [consumed, :slot | split_fragment(rest, "")] - - defp split_fragment(<>, consumed), - do: split_fragment(rest, consumed <> <>) - - defp split_fragment(<>, consumed), - do: split_fragment(rest, consumed <> <>) -end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index cbe2c7d2..1d2c9796 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -20,11 +20,11 @@ defmodule AshPostgres.MigrationGenerator do check: false, drop_columns: false - def generate(apis, opts \\ []) do - apis = List.wrap(apis) + def generate(domains, opts \\ []) do + domains = List.wrap(domains) opts = opts(opts) - all_resources = Enum.uniq(Enum.flat_map(apis, &Ash.Api.Info.resources/1)) + all_resources = Enum.uniq(Enum.flat_map(domains, &Ash.Domain.Info.resources/1)) {tenant_snapshots, snapshots} = all_resources @@ -60,8 +60,8 @@ defmodule AshPostgres.MigrationGenerator do Does not support everything supported by the migration generator. """ - def take_snapshots(api, repo, only_resources \\ nil) do - all_resources = api |> Ash.Api.Info.resources() |> Enum.uniq() + def take_snapshots(domain, repo, only_resources \\ nil) do + all_resources = domain |> Ash.Domain.Info.resources() |> Enum.uniq() all_resources |> Enum.filter(fn resource -> diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index bc6c8bc5..2a4ea757 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -1,6 +1,6 @@ defmodule AshPostgres.MixHelpers do @moduledoc false - def apis!(opts, args) do + def domains!(opts, args) do apps = if apps_paths = Mix.Project.apps_paths() do apps_paths |> Map.keys() |> Enum.sort() @@ -8,46 +8,46 @@ defmodule AshPostgres.MixHelpers do [Mix.Project.config()[:app]] end - configured_apis = Enum.flat_map(apps, &Application.get_env(&1, :ash_apis, [])) + configure_domains = Enum.flat_map(apps, &Application.get_env(&1, :ash_domains, [])) - apis = - if opts[:apis] && opts[:apis] != "" do - opts[:apis] + domains = + if opts[:domains] && opts[:domains] != "" do + opts[:domains] |> Kernel.||("") |> String.split(",") |> Enum.flat_map(fn "" -> [] - api -> - [Module.concat([api])] + domain -> + [Module.concat([domain])] end) else - configured_apis + configure_domains end - apis + domains |> Enum.map(&ensure_compiled(&1, args)) |> case do [] -> - raise "must supply the --apis argument, or set `config :my_app, ash_apis: [...]` in config" + raise "must supply the --domains argument, or set `config :my_app, ash_domains: [...]` in config" - apis -> - apis + domains -> + domains end end def repos!(opts, args) do - apis = apis!(opts, args) + domains = domains!(opts, args) resources = - apis - |> Enum.flat_map(&Ash.Api.Info.resources/1) + domains + |> Enum.flat_map(&Ash.Domain.Info.resources/1) |> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer)) |> case do [] -> raise """ - No resources with `data_layer: AshPostgres.DataLayer` found in the apis #{Enum.map_join(apis, ",", &inspect/1)}. + No resources with `data_layer: AshPostgres.DataLayer` found in the domains #{Enum.map_join(domains, ",", &inspect/1)}. Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`. """ @@ -64,7 +64,7 @@ defmodule AshPostgres.MixHelpers do |> case do [] -> raise """ - No repos could be found configured on the resources in the apis: #{Enum.map_join(apis, ",", &inspect/1)} + No repos could be found configured on the resources in the domains: #{Enum.map_join(domains, ",", &inspect/1)} At least one resource must have a repo configured. @@ -98,7 +98,7 @@ defmodule AshPostgres.MixHelpers do end end - defp ensure_compiled(api, args) do + defp ensure_compiled(domain, args) do if Code.ensure_loaded?(Mix.Tasks.App.Config) do Mix.Task.run("app.config", args) else @@ -106,18 +106,18 @@ defmodule AshPostgres.MixHelpers do "--no-compile" not in args && Mix.Task.run("compile", args) end - case Code.ensure_compiled(api) do + case Code.ensure_compiled(domain) do {:module, _} -> - api - |> Ash.Api.Info.resources() + domain + |> Ash.Domain.Info.resources() |> Enum.each(&Code.ensure_compiled/1) # TODO: We shouldn't need to make sure that the resources are compiled - api + domain {:error, error} -> - Mix.raise("Could not load #{inspect(api)}, error: #{inspect(error)}. ") + Mix.raise("Could not load #{inspect(domain)}, error: #{inspect(error)}. ") end end diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index bdfaa614..e4de6344 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -5,7 +5,7 @@ defmodule Mix.Tasks.AshPostgres.Create do @switches [ quiet: :boolean, - apis: :string, + domains: :string, no_compile: :boolean, no_deps_check: :boolean ] @@ -15,16 +15,16 @@ defmodule Mix.Tasks.AshPostgres.Create do ] @moduledoc """ - Create the storage for repos in all resources for the given (or configured) apis. + Create the storage for repos in all resources for the given (or configured) domains. ## Examples mix ash_postgres.create - mix ash_postgres.create --apis MyApp.Api1,MyApp.Api2 + mix ash_postgres.create --domains MyApp.Domain1,MyApp.Domain2 ## Command line options - * `--apis` - the apis who's repos you want to migrate. + * `--domains` - the domains who's repos you want to migrate. * `--quiet` - do not log output * `--no-compile` - do not compile before creating * `--no-deps-check` - do not compile before creating @@ -41,7 +41,7 @@ defmodule Mix.Tasks.AshPostgres.Create do ["-r", to_string(repo)] end) - rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis") + rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains") Mix.Task.reenable("ecto.create") Mix.Task.run("ecto.create", repo_args ++ rest_opts) diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index af121b0b..b8ac91de 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -1,7 +1,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do use Mix.Task - @shortdoc "Drops the repository storage for the repos in the specified (or configured) apis" + @shortdoc "Drops the repository storage for the repos in the specified (or configured) domains" @default_opts [force: false, force_drop: false] @aliases [ @@ -13,7 +13,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do force: :boolean, force_drop: :boolean, quiet: :boolean, - apis: :string, + domains: :string, no_compile: :boolean, no_deps_check: :boolean ] @@ -24,11 +24,11 @@ defmodule Mix.Tasks.AshPostgres.Drop do ## Examples mix ash_postgres.drop - mix ash_postgres.drop -r MyApp.Api1,MyApp.Api2 + mix ash_postgres.drop -r MyApp.Repo1,MyApp.Repo2 ## Command line options - * `--apis` - the apis who's repos should be dropped + * `--domains` - the domains who's repos should be dropped * `-q`, `--quiet` - run the command quietly * `-f`, `--force` - do not ask for confirmation when dropping the database. Configuration is asked only when `:start_permanent` is set to true @@ -51,7 +51,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do ["-r", to_string(repo)] end) - rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis") + rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains") Mix.Task.reenable("ecto.drop") Mix.Task.run("ecto.drop", repo_args ++ rest_opts) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 4a3154d1..33ebb225 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do Options: - * `apis` - a comma separated list of API modules, for which migrations will be generated + * `domains` - a comma separated list of Domain modules, for which migrations will be generated * `snapshot-path` - a custom path to store the snapshots, defaults to "priv/resource_snapshots" * `migration-path` - a custom path to store the migrations, defaults to "priv". Migrations are stored in a folder for each repo, so `priv/repo_name/migrations` @@ -87,7 +87,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do {opts, _} = OptionParser.parse!(args, strict: [ - apis: :string, + domains: :string, snapshot_path: :string, migration_path: :string, tenant_migration_path: :string, @@ -100,7 +100,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do ] ) - apis = AshPostgres.MixHelpers.apis!(opts, args) + domains = AshPostgres.MixHelpers.domains!(opts, args) opts = opts @@ -119,6 +119,6 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do """) end - AshPostgres.MigrationGenerator.generate(apis, opts) + AshPostgres.MigrationGenerator.generate(domains, opts) end end diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index d3d3cb69..d4530254 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do import AshPostgres.MixHelpers, only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] - @shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) apis" + @shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) domains" @aliases [ n: :step @@ -20,7 +20,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do pool_size: :integer, log_sql: :boolean, strict_version_order: :boolean, - apis: :string, + domains: :string, no_compile: :boolean, no_deps_check: :boolean, migrations_path: :keep, @@ -42,7 +42,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do specific version number, supply `--to version_number`. To migrate a specific number of times, use `--step n`. - This is only really useful if your api or apis only use a single repo. + This is only really useful if your domains only use a single repo. If you have multiple repos and you want to run a single migration and/or migrate/roll them back to different points, you will need to use the ecto specific task, `mix ecto.migrate` and provide your repo name. @@ -53,7 +53,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do ## Examples mix ash_postgres.migrate - mix ash_postgres.migrate --apis MyApp.Api1,MyApp.Api2 + mix ash_postgres.migrate --domains MyApp.Domain1,MyApp.Domain2 mix ash_postgres.migrate -n 3 mix ash_postgres.migrate --step 3 @@ -62,7 +62,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do ## Command line options - * `--apis` - the apis who's repos should be migrated + * `--domains` - the domains who's repos should be migrated * `--tenants` - Run the tenant migrations @@ -107,7 +107,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do rest_opts = args - |> AshPostgres.MixHelpers.delete_arg("--apis") + |> AshPostgres.MixHelpers.delete_arg("--domains") |> AshPostgres.MixHelpers.delete_arg("--migrations-path") |> AshPostgres.MixHelpers.delete_flag("--tenants") |> AshPostgres.MixHelpers.delete_flag("--only-tenants") diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index ecb47657..b01d91d8 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do import AshPostgres.MixHelpers, only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] - @shortdoc "Rolls back the repository migrations for all repositories in the provided (or configured) apis" + @shortdoc "Rolls back the repository migrations for all repositories in the provided (or configured) domains" @moduledoc """ Reverts applied migrations in the given repository. @@ -16,7 +16,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do specific number of times, use `--step n`. To undo all applied migrations, provide `--all`. - This is only really useful if your api or apis only use a single repo. + This is only really useful if your domains only use a single repo. If you have multiple repos and you want to run a single migration and/or migrate/roll them back to different points, you will need to use the ecto specific task, `mix ecto.migrate` and provide your repo name. @@ -30,7 +30,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do mix ash_postgres.rollback --to 20080906120000 ## Command line options - * `--apis` - the apis who's repos should be rolledback + * `--domains` - the domains who's repos should be rolledback * `--all` - revert all applied migrations * `--step` / `-n` - revert n number of applied migrations * `--to` / `-v` - revert all migrations down to and including version @@ -66,7 +66,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do rest_opts = args - |> AshPostgres.MixHelpers.delete_arg("--apis") + |> AshPostgres.MixHelpers.delete_arg("--domains") |> AshPostgres.MixHelpers.delete_arg("--migrations-path") |> AshPostgres.MixHelpers.delete_flag("--tenants") |> AshPostgres.MixHelpers.delete_flag("--only-tenants") diff --git a/lib/repo.ex b/lib/repo.ex index 01b6ceb3..78e3bb8b 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -44,6 +44,9 @@ defmodule AshPostgres.Repo do @doc "Use this to inform the data layer about what extensions are installed" @callback installed_extensions() :: [String.t() | module()] + @doc "Configure the version of postgres that is being used." + @callback pg_version() :: Version.t() + @doc """ Use this to inform the data layer about the oldest potential postgres version it will be run on. @@ -54,7 +57,6 @@ defmodule AshPostgres.Repo do For things like `Fly.Repo`, where you might need to have more fine grained control over the repo module, you can use the `define_ecto_repo?: false` option to `use AshPostgres.Repo`. """ - @callback min_pg_version() :: integer() @callback on_transaction_begin(reason :: Ash.DataLayer.transaction_reason()) :: term @@ -66,6 +68,7 @@ defmodule AshPostgres.Repo do @callback migrations_path() :: String.t() | nil @doc "The default prefix(postgres schema) to use when building queries" @callback default_prefix() :: String.t() + @doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields" @callback override_migration_type(atom) :: atom @@ -88,7 +91,6 @@ defmodule AshPostgres.Repo do def migrations_path, do: nil def default_prefix, do: "public" def override_migration_type(type), do: type - def min_pg_version, do: 10 def transaction!(fun) do case fun.() do @@ -224,8 +226,7 @@ defmodule AshPostgres.Repo do all_tenants: 0, tenant_migrations_path: 0, default_prefix: 0, - override_migration_type: 1, - min_pg_version: 0 + override_migration_type: 1 end end end diff --git a/lib/transformers/ensure_table_or_polymorphic.ex b/lib/verifiers/ensure_table_or_polymorphic.ex similarity index 51% rename from lib/transformers/ensure_table_or_polymorphic.ex rename to lib/verifiers/ensure_table_or_polymorphic.ex index 064d0262..1f60b8ed 100644 --- a/lib/transformers/ensure_table_or_polymorphic.ex +++ b/lib/verifiers/ensure_table_or_polymorphic.ex @@ -1,14 +1,14 @@ -defmodule AshPostgres.Transformers.EnsureTableOrPolymorphic do +defmodule AshPostgres.Verifiers.EnsureTableOrPolymorphic do @moduledoc false - use Spark.Dsl.Transformer - alias Spark.Dsl.Transformer + use Spark.Dsl.Verifier + alias Spark.Dsl.Verifier - def transform(dsl) do - if Transformer.get_option(dsl, [:postgres], :polymorphic?) || - Transformer.get_option(dsl, [:postgres], :table) do - {:ok, dsl} + def verify(dsl) do + if Verifier.get_option(dsl, [:postgres], :polymorphic?) || + Verifier.get_option(dsl, [:postgres], :table) do + :ok else - resource = Transformer.get_persisted(dsl, :module) + resource = Verifier.get_persisted(dsl, :module) raise Spark.Error.DslError, module: resource, diff --git a/lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex similarity index 81% rename from lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex rename to lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex index b3b02e8b..0d9e71c8 100644 --- a/lib/transformers/prevent_attribute_multitenancy_and_non_full_match_type.ex +++ b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex @@ -1,10 +1,10 @@ -defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchType do +defmodule AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType do @moduledoc false - use Spark.Dsl.Transformer - alias Spark.Dsl.Transformer + use Spark.Dsl.Verifier + alias Spark.Dsl.Verifier - def transform(dsl) do - if Transformer.get_option(dsl, [:multitenancy], :strategy) == :attribute do + def verify(dsl) do + if Verifier.get_option(dsl, [:multitenancy], :strategy) == :attribute do dsl |> AshPostgres.DataLayer.Info.references() |> Enum.filter(&(&1.match_type && &1.match_type != :full)) @@ -14,7 +14,7 @@ defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchTy if uses_attribute_strategy?(relationship) and not targets_primary_key?(relationship) and not targets_multitenancy_attribute?(relationship) do - resource = Transformer.get_persisted(dsl, :module) + resource = Verifier.get_persisted(dsl, :module) raise Spark.Error.DslError, module: resource, @@ -28,9 +28,9 @@ defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchTy :ok end end) - else - {:ok, dsl} end + + :ok end defp uses_attribute_strategy?(relationship) do diff --git a/lib/transformers/prevent_multidimensional_array_aggregates.ex b/lib/verifiers/prevent_multidimensional_array_aggregates.ex similarity index 80% rename from lib/transformers/prevent_multidimensional_array_aggregates.ex rename to lib/verifiers/prevent_multidimensional_array_aggregates.ex index 62bfa736..6b267ff4 100644 --- a/lib/transformers/prevent_multidimensional_array_aggregates.ex +++ b/lib/verifiers/prevent_multidimensional_array_aggregates.ex @@ -1,12 +1,10 @@ -defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do +defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do @moduledoc false - use Spark.Dsl.Transformer - alias Spark.Dsl.Transformer + use Spark.Dsl.Verifier + alias Spark.Dsl.Verifier - def after_compile?, do: true - - def transform(dsl) do - resource = Transformer.get_persisted(dsl, :module) + def verify(dsl) do + resource = Verifier.get_persisted(dsl, :module) dsl |> Ash.Resource.Info.aggregates() @@ -35,6 +33,6 @@ defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do end end) - {:ok, dsl} + :ok end end diff --git a/lib/transformers/validate_references.ex b/lib/verifiers/validate_references.ex similarity index 65% rename from lib/transformers/validate_references.ex rename to lib/verifiers/validate_references.ex index 0a46e6d2..0bb6955c 100644 --- a/lib/transformers/validate_references.ex +++ b/lib/verifiers/validate_references.ex @@ -1,23 +1,21 @@ -defmodule AshPostgres.Transformers.ValidateReferences do +defmodule AshPostgres.Verifiers.ValidateReferences do @moduledoc false - use Spark.Dsl.Transformer - alias Spark.Dsl.Transformer + use Spark.Dsl.Verifier + alias Spark.Dsl.Verifier - def after_compile?, do: true - - def transform(dsl) do + def verify(dsl) do dsl |> AshPostgres.DataLayer.Info.references() |> Enum.each(fn reference -> unless Ash.Resource.Info.relationship(dsl, reference.relationship) do raise Spark.Error.DslError, path: [:postgres, :references, reference.relationship], - module: Transformer.get_persisted(dsl, :module), + module: Verifier.get_persisted(dsl, :module), message: "Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists" end end) - {:ok, dsl} + :ok end end diff --git a/lib/verifiers/verify_postgres_version.ex b/lib/verifiers/verify_postgres_version.ex new file mode 100644 index 00000000..0c7bad9d --- /dev/null +++ b/lib/verifiers/verify_postgres_version.ex @@ -0,0 +1,37 @@ +defmodule AshPostgres.Verifiers.VerifyPostgresVersion do + @moduledoc false + use Spark.Dsl.Verifier + + def verify(dsl) do + read_repo = AshPostgres.DataLayer.Info.repo(dsl, :read) + mutate_repo = AshPostgres.DataLayer.Info.repo(dsl, :mutate) + + read_version = + read_repo.pg_version() |> parse!(read_repo) + + mutation_version = mutate_repo.pg_version() |> parse!(mutate_repo) + + if Version.match?(read_version, ">= 14.0.0") && Version.match?(mutation_version, ">= 14.0.0") do + :ok + else + {:error, "AshPostgres now only supports versions >= 14.0."} + end + end + + defp parse!(%Version{} = version, _repo) do + version + end + + defp parse!(version, repo) do + Version.parse!(version) + rescue + e -> + reraise ArgumentError, + """ + Failed to parse version in `#{inspect(repo)}.pg_version()`: #{inspect(version)} + + Error: #{Exception.message(e)} + """, + __STACKTRACE__ + end +end diff --git a/mix.exs b/mix.exs index 19c0d256..7df3a322 100644 --- a/mix.exs +++ b/mix.exs @@ -45,7 +45,7 @@ defmodule AshPostgres.MixProject do if Mix.env() == :test do def application() do [ - applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee], + applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee, :xmerl], mod: {AshPostgres.TestApp, []} ] end @@ -140,7 +140,6 @@ defmodule AshPostgres.MixProject do EctoMigrationDefault ], Expressions: [ - AshPostgres.Functions.Fragment, AshPostgres.Functions.TrigramSimilarity, AshPostgres.Functions.ILike, AshPostgres.Functions.Like, @@ -157,7 +156,10 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 2.19 and >= 2.20.3")}, + {:spark, path: "../spark", override: true}, + # dev/test dependencies + {:simple_sat, "~> 0.1"}, + {:ash, ash_version(github: "ash-project/ash", branch: "3.0")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 3f15e7d4..da3f1f08 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.20.3", "2ded1295fd20e2a45b01c678fe93c51397384ec5e5e4babc80f1ae9ce896ca82", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.6", [hex: :reactor, repo: "hexpm", optional: false]}, {:spark, ">= 1.1.55 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b53374c6c70da21bb8d53fefb88e1b0dc7a6fd8cf48ecaff4d6e57d2e69afbca"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "37587cbc580c30248044eea01d461f578df6cbc4", [branch: "3.0"]}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -8,11 +8,9 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "elixir_make": {:hex, :elixir_make, "0.8.2", "cd4a5a75891362e9207adaac7e66223fd256ec2518ae013af7f10c9c85b50b5c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "9d9607d640c372a7291e5a56ce655aa2351897929be20bd211648fdb79e725dc"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, @@ -26,14 +24,14 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, - "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, + "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, + "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, + "spark": {:hex, :spark, "2.1.6", "15c6725836607322e867b40fb6bdeb13f4606d48a001d2a74518559678e26895", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "6838d226e83acedb3a6169169bfc2d7afde19ef6a37fb14f0572d39db9d56c7b"}, + "splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 162c3e6f..1d955159 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1,37 +1,37 @@ defmodule AshPostgres.AggregateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Author, Comment, Organization, Post, Rating, User} + alias AshPostgres.Test.{Author, Comment, Organization, Post, Rating, User} require Ash.Query - require Ash.Expr + import Ash.Expr test "relates to actor via has_many and with an aggregate" do org = Organization - |> Ash.Changeset.new(%{name: "The Org"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{name: "The Org"}) + |> Ash.create!() post = Post |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() user = User |> Ash.Changeset.for_create(:create, %{}) |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() read_post = Post |> Ash.Query.filter(id == ^post.id) - |> Api.read_one!(actor: user) + |> Ash.read_one!(actor: user) assert read_post.id == post.id @@ -39,13 +39,13 @@ defmodule AshPostgres.AggregateTest do Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:count_of_comments) - |> Api.read_one!(actor: user) + |> Ash.read_one!(actor: user) assert read_post.count_of_comments == 1 read_post = post - |> Api.load!(:count_of_comments, actor: user) + |> Ash.load!(:count_of_comments, actor: user) assert read_post.count_of_comments == 1 end @@ -53,70 +53,70 @@ defmodule AshPostgres.AggregateTest do describe "join filters" do test "with no data, it does not effect the behavior" do Author - |> Ash.Changeset.new(%{}) - |> Api.create!() + |> Ash.Changeset.for_create(:create) + |> Ash.create!() assert [%{count_of_posts_with_better_comment: 0}] = Author |> Ash.Query.load(:count_of_posts_with_better_comment) - |> Api.read!() + |> Ash.read!() end test "it properly applies join criteria" do author = Author - |> Ash.Changeset.new(%{}) - |> Api.create!() + |> Ash.Changeset.for_create(:create) + |> Ash.create!() matching_post = Post - |> Ash.Changeset.new(%{title: "match", score: 10}) + |> Ash.Changeset.for_create(:create, %{title: "match", score: 10}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() non_matching_post = Post - |> Ash.Changeset.new(%{title: "non_match", score: 100}) + |> Ash.Changeset.for_create(:create, %{title: "non_match", score: 100}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match", likes: 100}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 100}) |> Ash.Changeset.manage_relationship(:post, matching_post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non_match", likes: 0}) + |> Ash.Changeset.for_create(:create, %{title: "non_match", likes: 0}) |> Ash.Changeset.manage_relationship(:post, non_matching_post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{count_of_posts_with_better_comment: 1}] = Author |> Ash.Query.load(:count_of_posts_with_better_comment) - |> Api.read!() + |> Ash.read!() end test "it properly applies join criteria to exists queries in filters" do author = Author - |> Ash.Changeset.new(%{}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() non_matching_post = Post - |> Ash.Changeset.new(%{title: "non_match", score: 100}) + |> Ash.Changeset.for_create(:create, %{title: "non_match", score: 100}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non_match", likes: 0}) + |> Ash.Changeset.for_create(:create, %{title: "non_match", likes: 0}) |> Ash.Changeset.manage_relationship(:post, non_matching_post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [] = Author |> Ash.Query.filter(has_post_with_better_comment) - |> Api.read!() + |> Ash.read!() end end @@ -124,33 +124,31 @@ defmodule AshPostgres.AggregateTest do test "with no related data it returns 0" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert %{count_of_comments: 0} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:count_of_comments) - |> Api.read_one!() + |> Ash.read_one!() end test "with data and a custom aggregate, it returns the count" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() - - import Ash.Query + |> Ash.create!() assert %{aggregates: %{custom_count_of_comments: 1}} = Post @@ -161,12 +159,12 @@ defmodule AshPostgres.AggregateTest do :comments, query: [filter: expr(not is_nil(title))] ) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{aggregates: %{custom_count_of_comments: 2}} = Post @@ -177,26 +175,24 @@ defmodule AshPostgres.AggregateTest do :comments, query: [filter: expr(not is_nil(title))] ) - |> Api.read_one!() + |> Ash.read_one!() end test "with data and a custom string keyed aggregate, it returns the count" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() - - import Ash.Query + |> Ash.create!() assert %{aggregates: %{"custom_count_of_comments" => 1}} = Post @@ -207,36 +203,36 @@ defmodule AshPostgres.AggregateTest do :comments, query: [filter: expr(not is_nil(title))] ) - |> Api.read_one!() + |> Ash.read_one!() end test "with data for a many_to_many, it returns the count" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() post3 = Post - |> Ash.Changeset.new(%{title: "title3"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title3"}) + |> Ash.create!() post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove ) - |> Api.update!() + |> Ash.update!() post2 |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove) - |> Api.update!() + |> Ash.update!() assert [ %{count_of_linked_posts: 2, title: "title"}, @@ -246,36 +242,36 @@ defmodule AshPostgres.AggregateTest do |> Ash.Query.load(:count_of_linked_posts) |> Ash.Query.filter(count_of_linked_posts >= 1) |> Ash.Query.sort(count_of_linked_posts: :desc) - |> Api.read!() + |> Ash.read!() end test "with data and a filter, it returns the count" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{count_of_comments_called_match: 1} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:count_of_comments_called_match) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "not_match"}) + |> Ash.Changeset.for_create(:create, %{title: "not_match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{count_of_comments_called_match: 1} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:count_of_comments_called_match) - |> Api.read_one!() + |> Ash.read_one!() end end @@ -283,81 +279,81 @@ defmodule AshPostgres.AggregateTest do test "with data and a filter, it returns the correct result" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non-match"}) + |> Ash.Changeset.for_create(:create, %{title: "non-match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{has_comment_called_match: false} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:has_comment_called_match) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{has_comment_called_match: true} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:has_comment_called_match) - |> Api.read_one!() + |> Ash.read_one!() end test "exists aggregates can be referenced in filters" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() refute Post |> Ash.Query.filter(has_comment_called_match) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{has_comment_called_match: true} = Post |> Ash.Query.filter(has_comment_called_match) |> Ash.Query.load(:has_comment_called_match) - |> Api.read_one!() + |> Ash.read_one!() end test "exists aggregates can be used at the query level" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() refute Post |> Ash.Query.filter(has_comment_called_match) - |> Api.exists?() + |> Ash.exists?() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() - assert Post |> Api.exists?() + assert Post |> Ash.exists?() - refute Post |> Api.exists?(query: [filter: [title: "non-match"]]) + refute Post |> Ash.exists?(query: [filter: [title: "non-match"]]) end end @@ -365,88 +361,88 @@ defmodule AshPostgres.AggregateTest do test "with no related data it returns an empty list" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert %{comment_titles: []} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:comment_titles) - |> Api.read_one!() + |> Ash.read_one!() end test "with related data, it returns the value" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "bbb"}) + |> Ash.Changeset.for_create(:create, %{title: "bbb"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "ccc"}) + |> Ash.Changeset.for_create(:create, %{title: "ccc"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{comment_titles: ["bbb", "ccc"]} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:comment_titles) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "aaa"}) + |> Ash.Changeset.for_create(:create, %{title: "aaa"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{comment_titles: ["aaa", "bbb", "ccc"]} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:comment_titles) - |> Api.read_one!() + |> Ash.read_one!() end test "with related data, it returns the uniq" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "aaa"}) + |> Ash.Changeset.for_create(:create, %{title: "aaa"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "aaa"}) + |> Ash.Changeset.for_create(:create, %{title: "aaa"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{uniq_comment_titles: ["aaa"]} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:uniq_comment_titles) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "bbb"}) + |> Ash.Changeset.for_create(:create, %{title: "bbb"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{uniq_comment_titles: ["aaa", "bbb"]} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:uniq_comment_titles) - |> Api.read_one!() + |> Ash.read_one!() assert %{count_comment_titles: 3, count_uniq_comment_titles: 2} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load([:count_comment_titles, :count_uniq_comment_titles]) - |> Api.read_one!() + |> Ash.read_one!() end end @@ -454,118 +450,118 @@ defmodule AshPostgres.AggregateTest do test "with no related data it returns nil" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert %{first_comment: nil} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:first_comment) - |> Api.read_one!() + |> Ash.read_one!() end test "with related data, it returns the value" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{first_comment: "match"} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:first_comment) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "early match"}) + |> Ash.Changeset.for_create(:create, %{title: "early match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{first_comment: "early match"} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:first_comment) - |> Api.read_one!() + |> Ash.read_one!() end test "it can be sorted on" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() post_id = post.id Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post_2 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "zed"}) + |> Ash.Changeset.for_create(:create, %{title: "zed"}) |> Ash.Changeset.manage_relationship(:post, post_2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{id: ^post_id} = Post |> Ash.Query.sort(:first_comment) |> Ash.Query.limit(1) - |> Api.read_one!() + |> Ash.read_one!() end test "first aggregates can be sorted on" do author = Author - |> Ash.Changeset.new(%{first_name: "first name"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "first name"}) + |> Ash.create!() post = Post - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{author_first_name: "first name"} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:author_first_name) |> Ash.Query.sort(author_first_name: :asc) - |> Api.read_one!() + |> Ash.read_one!() end test "aggregate maintains datetime precision" do author = Author - |> Ash.Changeset.new(%{first_name: "first name"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "first name"}) + |> Ash.create!() post = Post - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() latest_comment = Comment - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() fetched_post = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:latest_comment_created_at) - |> Api.read_one!() + |> Ash.read_one!() assert latest_comment.created_at == fetched_post.latest_comment_created_at end @@ -573,48 +569,48 @@ defmodule AshPostgres.AggregateTest do test "it can be sorted on and produces the appropriate order" do post1 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:post, post1, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "c"}) + |> Ash.Changeset.for_create(:create, %{title: "c"}) |> Ash.Changeset.manage_relationship(:post, post1, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "a"}) + |> Ash.Changeset.for_create(:create, %{title: "a"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post3 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "c"}) + |> Ash.Changeset.for_create(:create, %{title: "c"}) |> Ash.Changeset.manage_relationship(:post, post3, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "d"}) + |> Ash.Changeset.for_create(:create, %{title: "d"}) |> Ash.Changeset.manage_relationship(:post, post3, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{last_comment: "d"}, %{last_comment: "c"}] = Post @@ -622,15 +618,15 @@ defmodule AshPostgres.AggregateTest do |> Ash.Query.sort(last_comment: :desc) |> Ash.Query.filter(not is_nil(comments.title)) |> Ash.Query.limit(2) - |> Api.read!() + |> Ash.read!() end end test "sum aggregates show the same value with filters on the sum vs filters on relationships" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() for i <- 1..5 do ratings = @@ -639,22 +635,21 @@ defmodule AshPostgres.AggregateTest do end Comment - |> Ash.Changeset.new(%{title: "title#{i}"}) + |> Ash.Changeset.for_create(:create, %{title: "title#{i}"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:ratings, ratings, type: :create) - |> Api.create!() + |> Ash.create!() end values = post - |> Api.load!([ - :sum_of_popular_comment_rating_scores, + |> Ash.load!([ :sum_of_popular_comment_rating_scores_2 ]) - |> Map.take([:sum_of_popular_comment_rating_scores, :sum_of_popular_comment_rating_scores_2]) - |> Map.values() + |> Map.take([:sum_of_popular_comment_rating_scores_2]) - assert [80, 80] = values + assert %{sum_of_popular_comment_rating_scores_2: 80} = + values end test "can't define multidimensional array aggregate types" do @@ -662,6 +657,7 @@ defmodule AshPostgres.AggregateTest do defmodule Foo do @moduledoc false use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer postgres do @@ -678,7 +674,9 @@ defmodule AshPostgres.AggregateTest do end relationships do - belongs_to(:author, AshPostgres.Test.Author) + belongs_to(:author, AshPostgres.Test.Author) do + public?(true) + end end aggregates do @@ -691,277 +689,277 @@ defmodule AshPostgres.AggregateTest do test "related aggregates can be filtered on" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non_match"}) + |> Ash.Changeset.for_create(:create, %{title: "non_match"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non_match2"}) + |> Ash.Changeset.for_create(:create, %{title: "non_match2"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{title: "match"} = Comment |> Ash.Query.filter(post.count_of_comments == 1) - |> Api.read_one!() + |> Ash.read_one!() end describe "sum" do test "with no related data it returns nil" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert %{sum_of_comment_likes: nil} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes) - |> Api.read_one!() + |> Ash.read_one!() end test "with no related data and a default it returns the default" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert %{sum_of_comment_likes_with_default: 0} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes_with_default) - |> Api.read_one!() + |> Ash.read_one!() end test "with data, it returns the sum" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{sum_of_comment_likes: 2} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "match", likes: 3}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 3}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{sum_of_comment_likes: 5} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes) - |> Api.read_one!() + |> Ash.read_one!() end test "with data and a filter, it returns the sum" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{sum_of_comment_likes_called_match: 2} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes_called_match) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "not_match", likes: 3}) + |> Ash.Changeset.for_create(:create, %{title: "not_match", likes: 3}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{sum_of_comment_likes_called_match: 2} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes_called_match) - |> Api.read_one!() + |> Ash.read_one!() end test "filtering on a nested aggregate works" do Post |> Ash.Query.filter(count_of_comment_ratings == 0) - |> Api.read!() + |> Ash.read!() end test "nested aggregates show the proper values" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() author = AshPostgres.Test.Author - |> Ash.Changeset.new(%{"first_name" => "ted"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{"first_name" => "ted"}) + |> Ash.create!() comment1 = Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() comment2 = Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 5, resource_id: comment1.id}) + |> Ash.Changeset.for_create(:create, %{score: 5, resource_id: comment1.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 10, resource_id: comment2.id}) + |> Ash.Changeset.for_create(:create, %{score: 10, resource_id: comment2.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() assert [%{count_of_comment_ratings: 2}] = - Post |> Ash.Query.load(:count_of_comment_ratings) |> Api.read!() + Post |> Ash.Query.load(:count_of_comment_ratings) |> Ash.read!() assert [%{highest_comment_rating: 10}] = - Post |> Ash.Query.load(:highest_comment_rating) |> Api.read!() + Post |> Ash.Query.load(:highest_comment_rating) |> Ash.read!() assert [%{lowest_comment_rating: 5}] = - Post |> Ash.Query.load(:lowest_comment_rating) |> Api.read!() + Post |> Ash.Query.load(:lowest_comment_rating) |> Ash.read!() assert [%{avg_comment_rating: 7.5}] = - Post |> Ash.Query.load(:avg_comment_rating) |> Api.read!() + Post |> Ash.Query.load(:avg_comment_rating) |> Ash.read!() # TODO: want to add an option for `unique` here at some point assert [%{comment_authors: "ted,ted"}] = - Post |> Ash.Query.load(:comment_authors) |> Api.read!() + Post |> Ash.Query.load(:comment_authors) |> Ash.read!() end test "nested filtered aggregates show the proper values" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() comment1 = Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() comment2 = Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 20, resource_id: comment1.id}) + |> Ash.Changeset.for_create(:create, %{score: 20, resource_id: comment1.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 1, resource_id: comment2.id}) + |> Ash.Changeset.for_create(:create, %{score: 1, resource_id: comment2.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() assert [%{count_of_comment_ratings: 2, count_of_popular_comment_ratings: 1}] = Post |> Ash.Query.load([:count_of_comment_ratings, :count_of_popular_comment_ratings]) - |> Api.read!() + |> Ash.read!() assert [%{count_of_comment_ratings: 2}] = Post |> Ash.Query.load([:count_of_comment_ratings]) - |> Api.read!() + |> Ash.read!() assert [%{count_of_popular_comment_ratings: 1}] = Post |> Ash.Query.load([:count_of_popular_comment_ratings]) - |> Api.read!() + |> Ash.read!() end test "nested filtered and sorted aggregates show the proper values" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() comment1 = Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() comment2 = Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 20, resource_id: comment1.id}) + |> Ash.Changeset.for_create(:create, %{score: 20, resource_id: comment1.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 1, resource_id: comment2.id}) + |> Ash.Changeset.for_create(:create, %{score: 1, resource_id: comment2.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() assert [%{count_of_comment_ratings: 2, count_of_popular_comment_ratings: 1}] = Post |> Ash.Query.load([:count_of_comment_ratings, :count_of_popular_comment_ratings]) - |> Api.read!() + |> Ash.read!() end test "nested first aggregate works" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() comment = Comment - |> Ash.Changeset.new(%{title: "title", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "title", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Rating - |> Ash.Changeset.new(%{score: 10, resource_id: comment.id}) + |> Ash.Changeset.for_create(:create, %{score: 10, resource_id: comment.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() post = Post |> Ash.Query.load(:highest_rating) - |> Api.read!() + |> Ash.read!() |> Enum.at(0) assert post.highest_rating == 10 @@ -970,17 +968,17 @@ defmodule AshPostgres.AggregateTest do test "loading a nested aggregate works" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "title", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post |> Ash.Query.load(:count_of_comment_ratings) - |> Api.read!() + |> Ash.read!() |> Enum.map(fn post -> assert post.count_of_comment_ratings == 0 end) @@ -989,55 +987,55 @@ defmodule AshPostgres.AggregateTest do test "the sum can be filtered on when paginating" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{sum_of_comment_likes_called_match: 2} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes_called_match) - |> Api.read_one!() + |> Ash.read_one!() Comment - |> Ash.Changeset.new(%{title: "not_match", likes: 3}) + |> Ash.Changeset.for_create(:create, %{title: "not_match", likes: 3}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %Ash.Page.Offset{results: [%{sum_of_comment_likes_called_match: 2}]} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes_called_match) |> Ash.Query.filter(sum_of_comment_likes_called_match == 2) - |> Api.read!(action: :paginated, page: [limit: 1, count: true]) + |> Ash.read!(action: :paginated, page: [limit: 1, count: true]) assert %Ash.Page.Offset{results: []} = Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:sum_of_comment_likes_called_match) |> Ash.Query.filter(sum_of_comment_likes_called_match == 3) - |> Api.read!(action: :paginated, page: [limit: 1, count: true]) + |> Ash.read!(action: :paginated, page: [limit: 1, count: true]) end test "an aggregate on relationships with a filter returns the proper value" do post = Post - |> Ash.Changeset.new(%{title: "title", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", category: "foo"}) + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match", likes: 20}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match", likes: 17}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match", likes: 50}) @@ -1046,29 +1044,29 @@ defmodule AshPostgres.AggregateTest do DateTime.add(DateTime.utc_now(), :timer.hours(24) * -20, :second) ) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %Post{sum_of_recent_popular_comment_likes: 37} = Post |> Ash.Query.load(:sum_of_recent_popular_comment_likes) - |> Api.read_one!() + |> Ash.read_one!() end test "a count aggregate on relationships with a filter returns the proper value" do post = Post - |> Ash.Changeset.new(%{title: "title", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", category: "foo"}) + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match", likes: 20}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match", likes: 17}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match", likes: 50}) @@ -1077,142 +1075,142 @@ defmodule AshPostgres.AggregateTest do DateTime.add(DateTime.utc_now(), :timer.hours(24) * -20, :second) ) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %Post{count_of_recent_popular_comments: 2} = Post |> Ash.Query.load([ :count_of_recent_popular_comments ]) - |> Api.read_one!() + |> Ash.read_one!() end test "a count aggregate with a related filter returns the proper value" do post = Post - |> Ash.Changeset.new(%{title: "title", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", category: "foo"}) + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %Post{count_of_comments_that_have_a_post: 3} = Post |> Ash.Query.load([ :count_of_comments_that_have_a_post ]) - |> Api.read_one!() + |> Ash.read_one!() end test "a count aggregate with a related filter that uses `exists` returns the proper value" do post = Post - |> Ash.Changeset.new(%{title: "title", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", category: "foo"}) + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %Post{count_of_comments_that_have_a_post_with_exists: 3} = Post |> Ash.Query.load([ :count_of_comments_that_have_a_post_with_exists ]) - |> Api.read_one!() + |> Ash.read_one!() end test "a count with a filter that references a relationship that also has a filter" do post = Post - |> Ash.Changeset.new(%{title: "title", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", category: "foo"}) + |> Ash.create!() comment = Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() comment2 = Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Rating |> Ash.Changeset.for_create(:create, %{score: 10, resource_id: comment.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() Rating |> Ash.Changeset.for_create(:create, %{score: 1, resource_id: comment2.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() assert %Post{count_of_popular_comments: 1} = Post |> Ash.Query.load([ :count_of_popular_comments ]) - |> Api.read_one!() + |> Ash.read_one!() end test "a count with a filter that references a to many relationship can be aggregated at the query level" do Post |> Ash.Query.filter(comments.likes > 10) - |> Api.count!() + |> Ash.count!() end test "a list with a filter that references a to many relationship can be aggregated at the query level" do Post |> Ash.Query.filter(comments.likes > 10) - |> Api.list!(:title) + |> Ash.list!(:title) end test "a count with a limit and a filter can be aggregated at the query level" do Post - |> Ash.Changeset.new(%{title: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "foo"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "foo"}) + |> Ash.create!() assert 1 = Post |> Ash.Query.for_read(:title_is_foo) |> Ash.Query.limit(1) - |> Api.count!() + |> Ash.count!() end test "a count can filter independently of the query" do assert {:ok, %{count: 0, count2: 0}} = Post - |> Api.aggregate([ + |> Ash.aggregate([ {:count, :count, query: [filter: Ash.Expr.expr(comments.likes > 10)]}, {:count2, :count, query: [filter: Ash.Expr.expr(comments.likes < 10)]} ]) @@ -1221,7 +1219,7 @@ defmodule AshPostgres.AggregateTest do test "multiple aggregates will be grouped up if possible" do assert {:ok, %{count: 0, count2: 0}} = Post - |> Api.aggregate([ + |> Ash.aggregate([ {:count, :count, query: [ filter: @@ -1240,30 +1238,30 @@ defmodule AshPostgres.AggregateTest do test "a count with a filter that references a relationship combined with another" do post = Post - |> Ash.Changeset.new(%{title: "title", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", category: "foo"}) + |> Ash.create!() comment = Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() comment2 = Comment |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Rating |> Ash.Changeset.for_create(:create, %{score: 10, resource_id: comment.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() Rating |> Ash.Changeset.for_create(:create, %{score: 1, resource_id: comment2.id}) |> Ash.Changeset.set_context(%{data_layer: %{table: "comment_ratings"}}) - |> Api.create!() + |> Ash.create!() assert %Post{count_of_popular_comments: 1} = Post @@ -1271,7 +1269,7 @@ defmodule AshPostgres.AggregateTest do :count_of_comments, :count_of_popular_comments ]) - |> Api.read_one!() + |> Ash.read_one!() end end end diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index df0fc921..dfcf765d 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -3,8 +3,8 @@ defmodule AshPostgresTest do test "transaction metadata is given to on_transaction_begin" do AshPostgres.Test.Post - |> Ash.Changeset.new(%{title: "title"}) - |> AshPostgres.Test.Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert_receive %{ type: :create, @@ -15,8 +15,8 @@ defmodule AshPostgresTest do test "filter policies are applied" do post = AshPostgres.Test.Post - |> Ash.Changeset.new(%{title: "good"}) - |> AshPostgres.Test.Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "good"}) + |> Ash.create!() assert_raise Ash.Error.Forbidden, fn -> post @@ -24,13 +24,13 @@ defmodule AshPostgresTest do authorize?: true, actor: %{id: Ash.UUID.generate()} ) - |> AshPostgres.Test.Api.update!() + |> Ash.update!() |> Map.get(:title) end post |> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true) - |> AshPostgres.Test.Api.update!() + |> Ash.update!() |> Map.get(:title) end end diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 99fe0596..7f03507f 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.AtomicsTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post import Ash.Expr @@ -10,55 +10,57 @@ defmodule AshPostgres.AtomicsTest do Post |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) |> Ash.Changeset.atomic_update(:price, expr(price + 1)) - |> Api.create!() + |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) |> Ash.Changeset.atomic_update(:price, expr(price + 1)) - |> Api.create!() + |> Ash.create!() - assert [%{price: 2}] = Post |> Api.read!() + assert [%{price: 2}] = Post |> Ash.read!() end test "a basic atomic works" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) - |> Api.create!() + |> Ash.create!() assert %{price: 2} = post |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.atomic_update(:price, expr(price + 1)) - |> Api.update!() + |> Ash.update!() end test "an atomic works with a datetime" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) - |> Api.create!() + |> Ash.create!() now = DateTime.utc_now() assert %{created_at: ^now} = post - |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.new() |> Ash.Changeset.atomic_update(:created_at, expr(^now)) - |> Api.update!() + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.update!() end test "an atomic that violates a constraint will return the proper error" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) - |> Api.create!() + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/does not exist/, fn -> post - |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.new() |> Ash.Changeset.atomic_update(:organization_id, Ash.UUID.generate()) - |> Api.update!() + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.update!() end end @@ -66,13 +68,13 @@ defmodule AshPostgres.AtomicsTest do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) - |> Api.create!() + |> Ash.create!() post = post |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.atomic_update(:score, expr(score_after_winning)) - |> Api.update!() + |> Ash.update!() assert post.score == 1 end @@ -81,7 +83,7 @@ defmodule AshPostgres.AtomicsTest do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) - |> Api.create!() + |> Ash.create!() assert Post.increment_score!(post, 2).score == 2 diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index e5b301dd..b351c3d1 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -1,20 +1,20 @@ defmodule AshPostgres.BulkCreateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post describe "bulk creates" do test "bulk creates insert each input" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) assert [%{title: "fred"}, %{title: "george"}] = Post |> Ash.Query.sort(:title) - |> Api.read!() + |> Ash.read!() end test "bulk creates can be streamed" do assert [{:ok, %{title: "fred"}}, {:ok, %{title: "george"}}] = - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, return_stream?: true, return_records?: true ) @@ -26,7 +26,7 @@ defmodule AshPostgres.BulkCreateTest do {:ok, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 10}}, {:ok, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20}} ] = - Api.bulk_create!( + Ash.bulk_create!( [ %{title: "fred", uniq_one: "one", uniq_two: "two", price: 10}, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20} @@ -42,7 +42,7 @@ defmodule AshPostgres.BulkCreateTest do {:ok, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 1000}}, {:ok, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20_000}} ] = - Api.bulk_create!( + Ash.bulk_create!( [ %{title: "something", uniq_one: "one", uniq_two: "two", price: 1000}, %{title: "else", uniq_one: "three", uniq_two: "four", price: 20_000} @@ -76,7 +76,7 @@ defmodule AshPostgres.BulkCreateTest do # id: org_id, # title: "Avengers" # }) - # |> Api.create!() + # |> Ash.create!() # assert [ # {:ok, @@ -94,7 +94,7 @@ defmodule AshPostgres.BulkCreateTest do # organization_id: org_id # }} # ] = - # Api.bulk_create!( + # Ash.bulk_create!( # [ # %{ # name: "Tony Stark", @@ -135,7 +135,7 @@ defmodule AshPostgres.BulkCreateTest do # role: "master in chief" # }} # ] = - # Api.bulk_create!( + # Ash.bulk_create!( # [ # %{ # name: "Tony Stark", @@ -169,7 +169,7 @@ defmodule AshPostgres.BulkCreateTest do # end test "bulk creates can create relationships" do - Api.bulk_create!( + Ash.bulk_create!( [%{title: "fred", rating: %{score: 5}}, %{title: "george", rating: %{score: 0}}], Post, :create @@ -182,14 +182,14 @@ defmodule AshPostgres.BulkCreateTest do Post |> Ash.Query.sort(:title) |> Ash.Query.load(:ratings) - |> Api.read!() + |> Ash.read!() end end describe "validation errors" do test "skips invalid by default" do assert %{records: [_], errors: [_]} = - Api.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, + Ash.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, return_records?: true, return_errors?: true ) @@ -197,7 +197,7 @@ defmodule AshPostgres.BulkCreateTest do test "returns errors in the stream" do assert [{:ok, _}, {:error, _}] = - Api.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, + Ash.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, return_records?: true, return_stream?: true, return_errors?: true @@ -209,7 +209,7 @@ defmodule AshPostgres.BulkCreateTest do describe "database errors" do test "database errors affect the entire batch" do # assert %{records: [_], errors: [_]} = - Api.bulk_create( + Ash.bulk_create( [%{title: "fred"}, %{title: "george", organization_id: Ash.UUID.generate()}], Post, :create, @@ -219,11 +219,11 @@ defmodule AshPostgres.BulkCreateTest do assert [] = Post |> Ash.Query.sort(:title) - |> Api.read!() + |> Ash.read!() end test "database errors don't affect other batches" do - Api.bulk_create( + Ash.bulk_create( [%{title: "george", organization_id: Ash.UUID.generate()}, %{title: "fred"}], Post, :create, @@ -234,7 +234,7 @@ defmodule AshPostgres.BulkCreateTest do assert [%{title: "fred"}] = Post |> Ash.Query.sort(:title) - |> Api.read!() + |> Ash.read!() end end end diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index 6fe4c8a7..fa55a6a4 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -1,50 +1,50 @@ defmodule AshPostgres.BulkDestroyTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Expr require Ash.Query test "bulk destroys can run with nothing in the table" do - Api.bulk_destroy!(Post, :update, %{title: "new_title"}) + Ash.bulk_destroy!(Post, :update, %{title: "new_title"}) end test "bulk destroys destroy everything pertaining to the query" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Api.bulk_destroy!(Post, :update, %{}) + Ash.bulk_destroy!(Post, :update, %{}) - assert Api.read!(Post) == [] + assert Ash.read!(Post) == [] end test "bulk destroys only apply to things that the query produces" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post |> Ash.Query.filter(title == "fred") - |> Api.bulk_destroy!(:update, %{}) + |> Ash.bulk_destroy!(:update, %{}) # 😢 sad - assert [%{title: "george"}] = Api.read!(Post) + assert [%{title: "george"}] = Ash.read!(Post) end test "the query can join to related tables when necessary" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post |> Ash.Query.filter(author.first_name == "fred" or title == "fred") - |> Api.bulk_destroy!(:update, %{}, return_records?: true) + |> Ash.bulk_destroy!(:update, %{}, return_records?: true) - assert [%{title: "george"}] = Api.read!(Post) + assert [%{title: "george"}] = Ash.read!(Post) end test "bulk destroys can be done even on stream inputs" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post - |> Api.read!() - |> Api.bulk_destroy!(:destroy, %{}) + |> Ash.read!() + |> Ash.bulk_destroy!(:destroy, %{}, strategy: :stream, return_errors?: true) - assert [] = Api.read!(Post) + assert [] = Ash.read!(Post) end end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index d561167b..e2650df2 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -1,30 +1,30 @@ defmodule AshPostgres.BulkUpdateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Expr require Ash.Query test "bulk updates can run with nothing in the table" do - Api.bulk_update!(Post, :update, %{title: "new_title"}) + Ash.bulk_update!(Post, :update, %{title: "new_title"}) end test "bulk updates update everything pertaining to the query" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Api.bulk_update!(Post, :update, %{}, + Ash.bulk_update!(Post, :update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")} ) - posts = Api.read!(Post) + posts = Ash.read!(Post) assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff")) end test "a map can be given as input" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post - |> Api.bulk_update!( + |> Ash.bulk_update!( :update, %{list_of_stuff: [%{a: 1}]}, return_records?: true, @@ -36,25 +36,25 @@ defmodule AshPostgres.BulkUpdateTest do test "a map can be given as input on a regular update" do %{records: [post | _]} = - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, return_records?: true ) post |> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{a: [:a, :b]}, %{a: [:c, :d]}]}) - |> Api.update!() + |> Ash.update!() end test "bulk updates only apply to things that the query produces" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post |> Ash.Query.filter(title == "fred") - |> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) + |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) titles = Post - |> Api.read!() + |> Ash.read!() |> Enum.map(& &1.title) |> Enum.sort() @@ -62,15 +62,15 @@ defmodule AshPostgres.BulkUpdateTest do end test "the query can join to related tables when necessary" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post |> Ash.Query.filter(author.first_name == "fred" or title == "fred") - |> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) + |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) titles = Post - |> Api.read!() + |> Ash.read!() |> Enum.map(& &1.title) |> Enum.sort() @@ -78,18 +78,19 @@ defmodule AshPostgres.BulkUpdateTest do end test "bulk updates can be done even on stream inputs" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Post - |> Api.read!() - |> Api.bulk_update!(:update, %{}, + |> Ash.read!() + |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}, - return_records?: true + return_records?: true, + strategy: [:stream] ) titles = Post - |> Api.read!() + |> Ash.read!() |> Enum.map(& &1.title) |> Enum.sort() @@ -97,14 +98,17 @@ defmodule AshPostgres.BulkUpdateTest do end test "bulk updates that require initial data must use streaming" do - Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Post - |> Ash.Query.for_read(:paginated, authorize?: true) - |> Api.bulk_update!(:requires_initial_data, %{}, - authorize?: true, - allow_stream_with: :full_read, - authorize_query?: false - ) + assert_raise Ash.Error.Invalid, ~r/had no matching bulk strategy that could be used/, fn -> + Post + |> Ash.Query.for_read(:paginated, authorize?: true) + |> Ash.bulk_update!(:requires_initial_data, %{}, + authorize?: true, + allow_stream_with: :full_read, + authorize_query?: false, + return_errors?: true + ) + end end end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 3b6344de..c29224cf 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.CalculationTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Account, Api, Author, Comment, Post, User} + alias AshPostgres.Test.{Account, Author, Comment, Post, User} require Ash.Query import Ash.Expr @@ -8,48 +8,48 @@ defmodule AshPostgres.CalculationTest do test "an expression calculation can be filtered on" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() post3 = Post - |> Ash.Changeset.new(%{title: "title3"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title3"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "_"}) + |> Ash.Changeset.for_create(:create, %{title: "_"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "_"}) + |> Ash.Changeset.for_create(:create, %{title: "_"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "_"}) + |> Ash.Changeset.for_create(:create, %{title: "_"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove) - |> Api.update!() + |> Ash.update!() post2 |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove) - |> Api.update!() + |> Ash.update!() assert [%{c_times_p: 6, title: "match"}] = Post |> Ash.Query.load(:c_times_p) - |> Api.read!() + |> Ash.read!() |> Enum.filter(&(&1.c_times_p == 6)) assert [ @@ -57,12 +57,12 @@ defmodule AshPostgres.CalculationTest do ] = Post |> Ash.Query.filter(c_times_p == 6) - |> Api.read!() + |> Ash.read!() assert [] = Post |> Ash.Query.filter(author: [has_posts: true]) - |> Api.read!() + |> Ash.read!() end test "calculations can refer to to_one path attributes in filters" do @@ -72,18 +72,18 @@ defmodule AshPostgres.CalculationTest do first_name: "Foo", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{author_first_name_calc: "Foo"}] = Post |> Ash.Query.filter(author_first_name_calc == "Foo") |> Ash.Query.load(:author_first_name_calc) - |> Api.read!() + |> Ash.read!() end test "calculations can refer to to_one path attributes in sorts" do @@ -93,44 +93,44 @@ defmodule AshPostgres.CalculationTest do first_name: "Foo", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{author_first_name_calc: "Foo"}] = Post |> Ash.Query.sort(:author_first_name_calc) |> Ash.Query.load(:author_first_name_calc) - |> Api.read!() + |> Ash.read!() end test "calculations can refer to embedded attributes" do author = Author |> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}}) - |> Api.create!() + |> Ash.create!() assert %{title: "Mr."} = Author |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load(:title) - |> Api.read_one!() + |> Ash.read_one!() end test "calculations can use the || operator" do author = Author |> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}}) - |> Api.create!() + |> Ash.create!() assert %{first_name_or_bob: "bob"} = Author |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load(:first_name_or_bob) - |> Api.read_one!() + |> Ash.read_one!() end test "calculations can use the && operator" do @@ -140,60 +140,60 @@ defmodule AshPostgres.CalculationTest do first_name: "fred", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{first_name_and_bob: "bob"} = Author |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load(:first_name_and_bob) - |> Api.read_one!() + |> Ash.read_one!() end test "calculations can be used in related filters" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() post3 = Post - |> Ash.Changeset.new(%{title: "title3"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title3"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no_match"}) + |> Ash.Changeset.for_create(:create, %{title: "no_match"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove) - |> Api.update!() + |> Ash.update!() post2 |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove) - |> Api.update!() + |> Ash.update!() posts_query = Post @@ -202,7 +202,7 @@ defmodule AshPostgres.CalculationTest do assert %{post: %{c_times_p: 6}} = Comment |> Ash.Query.load(post: posts_query) - |> Api.read!() + |> Ash.read!() |> Enum.filter(&(&1.post.c_times_p == 6)) |> Enum.at(0) @@ -214,20 +214,20 @@ defmodule AshPostgres.CalculationTest do assert [ %{post: %{c_times_p: 6, title: "match"}} - ] = Api.read!(query) + ] = Ash.read!(query) - post |> Api.load!(:c_times_p) + post |> Ash.load!(:c_times_p) end test "concat calculation can be filtered on" do author = Author - |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"}) + |> Ash.create!() Author - |> Ash.Changeset.new(%{first_name: "not", last_name: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "not", last_name: "match"}) + |> Ash.create!() author_id = author.id @@ -235,60 +235,60 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.load(:full_name) |> Ash.Query.filter(full_name == "is match") - |> Api.read_one!() + |> Ash.read_one!() end test "calculations that refer to aggregates in comparison expressions can be filtered on" do Post |> Ash.Query.load(:has_future_comment) - |> Api.read!() + |> Ash.read!() end test "calculations that refer to aggregates can be authorized" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.for_create(:create, %{title: "comment"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{has_future_comment: false} = Post |> Ash.Query.load([:has_future_comment, :latest_comment_created_at]) |> Ash.Query.for_read(:allow_any, %{}) - |> Api.read_one!(authorize?: true) + |> Ash.read_one!(authorize?: true) assert %{has_future_comment: true} = Post |> Ash.Query.load([:has_future_comment, :latest_comment_created_at]) |> Ash.Query.for_read(:allow_any, %{}) - |> Api.read_one!(authorize?: false) + |> Ash.read_one!(authorize?: false) assert %{has_future_comment: false} = Post |> Ash.Query.for_read(:allow_any, %{}) - |> Api.read_one!() - |> Api.load!([:has_future_comment, :latest_comment_created_at], authorize?: true) + |> Ash.read_one!() + |> Ash.load!([:has_future_comment, :latest_comment_created_at], authorize?: true) assert %{has_future_comment: true} = Post |> Ash.Query.for_read(:allow_any, %{}) - |> Api.read_one!() - |> Api.load!([:has_future_comment, :latest_comment_created_at], authorize?: false) + |> Ash.read_one!() + |> Ash.load!([:has_future_comment, :latest_comment_created_at], authorize?: false) end test "conditional calculations can be filtered on" do author = Author - |> Ash.Changeset.new(%{first_name: "tom"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "tom"}) + |> Ash.create!() Author - |> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"}) + |> Ash.create!() author_id = author.id @@ -296,45 +296,45 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.load([:conditional_full_name, :full_name]) |> Ash.Query.filter(conditional_full_name == "(none)") - |> Api.read_one!() + |> Ash.read_one!() end test "parameterized calculations can be filtered on" do Author - |> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"}) + |> Ash.create!() assert %{param_full_name: "tom holland"} = Author |> Ash.Query.load(:param_full_name) - |> Api.read_one!() + |> Ash.read_one!() assert %{param_full_name: "tom~holland"} = Author |> Ash.Query.load(param_full_name: [separator: "~"]) - |> Api.read_one!() + |> Ash.read_one!() assert %{} = Author |> Ash.Query.filter(param_full_name(separator: "~") == "tom~holland") - |> Api.read_one!() + |> Ash.read_one!() end test "parameterized related calculations can be filtered on" do author = Author - |> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{title: "match"} = Comment |> Ash.Query.filter(author.param_full_name(separator: "~") == "tom~holland") - |> Api.read_one!() + |> Ash.read_one!() assert %{title: "match"} = Comment @@ -342,93 +342,97 @@ defmodule AshPostgres.CalculationTest do author.param_full_name(separator: "~") == "tom~holland" and author.param_full_name(separator: " ") == "tom holland" ) - |> Api.read_one!() + |> Ash.read_one!() end test "parameterized calculations can be sorted on" do Author - |> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"}) + |> Ash.create!() Author - |> Ash.Changeset.new(%{first_name: "abc", last_name: "def"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "abc", last_name: "def"}) + |> Ash.create!() assert [%{first_name: "abc"}, %{first_name: "tom"}] = Author |> Ash.Query.sort(param_full_name: [separator: "~"]) - |> Api.read!() + |> Ash.read!() end test "calculations using if and literal boolean results can run" do Post |> Ash.Query.load(:was_created_in_the_last_month) |> Ash.Query.filter(was_created_in_the_last_month == true) - |> Api.read!() + |> Ash.read!() end test "nested conditional calculations can be loaded" do Author - |> Ash.Changeset.new(%{last_name: "holland"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{last_name: "holland"}) + |> Ash.create!() Author - |> Ash.Changeset.new(%{first_name: "tom"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "tom"}) + |> Ash.create!() assert [%{nested_conditional: "No First Name"}, %{nested_conditional: "No Last Name"}] = Author |> Ash.Query.load(:nested_conditional) |> Ash.Query.sort(:nested_conditional) - |> Api.read!() + |> Ash.read!() end test "calculations load nullable timestamp aggregates compared to a fragment" do post = Post - |> Ash.Changeset.new(%{title: "aaa", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "aaa", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "bbb", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "aaa", likes: 1, arbitrary_timestamp: DateTime.now!("Etc/UTC")}) + |> Ash.Changeset.for_create(:create, %{ + title: "aaa", + likes: 1, + arbitrary_timestamp: DateTime.now!("Etc/UTC") + }) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "bbb", likes: 1}) + |> Ash.Changeset.for_create(:create, %{title: "bbb", likes: 1}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "aaa", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "aaa", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post |> Ash.Query.load([:has_future_arbitrary_timestamp]) - |> Api.read!() + |> Ash.read!() end test "loading a calculation loads its dependent loads" do user = User |> Ash.Changeset.for_create(:create, %{is_active: true}) - |> Api.create!() + |> Ash.create!() account = Account |> Ash.Changeset.for_create(:create, %{is_active: true}) |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) - |> Api.create!() - |> Api.load!([:active]) + |> Ash.create!() + |> Ash.load!([:active]) assert account.active end @@ -442,7 +446,7 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{ full_name_with_nils: "Bill Jones", @@ -452,7 +456,7 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load(:full_name_with_nils) |> Ash.Query.load(:full_name_with_nils_no_joiner) - |> Api.read_one!() + |> Ash.read_one!() end test "with nil value" do @@ -462,7 +466,7 @@ defmodule AshPostgres.CalculationTest do first_name: "Bill", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{ full_name_with_nils: "Bill", @@ -472,23 +476,23 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load(:full_name_with_nils) |> Ash.Query.load(:full_name_with_nils_no_joiner) - |> Api.read_one!() + |> Ash.read_one!() end end test "arguments with cast_in_query?: false are not cast" do Post - |> Ash.Changeset.new(%{title: "match", score: 42}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", score: 42}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "not", score: 42}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "not", score: 42}) + |> Ash.create!() assert [post] = Post |> Ash.Query.filter(similarity(search: expr(query(search: "match")))) - |> Api.read!() + |> Ash.read!() assert post.title == "match" end @@ -502,7 +506,7 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{ split_full_name: ["Bill", "Jones"] @@ -510,7 +514,7 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load(:split_full_name) - |> Api.read_one!() + |> Ash.read_one!() end test "trimming whitespace" do @@ -521,7 +525,7 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones ", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{ split_full_name_trim: ["Bill", "Jones"], @@ -530,37 +534,37 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load([:split_full_name_trim, :split_full_name]) - |> Api.read_one!() + |> Ash.read_one!() end end describe "count_nils/1" do test "counts nil values" do Post - |> Ash.Changeset.new(%{list_containing_nils: ["a", nil, "b", nil, "c"]}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{list_containing_nils: ["a", nil, "b", nil, "c"]}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{list_containing_nils: ["a", nil, "b", "c"]}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{list_containing_nils: ["a", nil, "b", "c"]}) + |> Ash.create!() assert [_] = Post |> Ash.Query.filter(count_nils(list_containing_nils) == 2) - |> Api.read!() + |> Ash.read!() end end describe "-/1" do test "makes numbers negative" do Post - |> Ash.Changeset.new(%{title: "match", score: 42}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", score: 42}) + |> Ash.create!() assert [%{negative_score: -42}] = Post |> Ash.Query.load(:negative_score) - |> Api.read!() + |> Ash.read!() end end @@ -568,39 +572,39 @@ defmodule AshPostgres.CalculationTest do test "maps can reference filtered aggregates" do post = Post - |> Ash.Changeset.new(%{title: "match", score: 42}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", score: 42}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "foo", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "foo", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "foo", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "foo", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "bar", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "bar", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{agg_map: %{called_foo: 2, called_bar: 1}}] = Post |> Ash.Query.load(:agg_map) - |> Api.read!() + |> Ash.read!() end test "maps can be constructed" do Post - |> Ash.Changeset.new(%{title: "match", score: 42}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", score: 42}) + |> Ash.create!() assert [%{score_map: %{negative_score: %{foo: -42}}}] = Post |> Ash.Query.load(:score_map) - |> Api.read!() + |> Ash.read!() end end @@ -613,7 +617,7 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones ", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{ first_name_from_split: "Bill" @@ -621,15 +625,15 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.filter(id == ^author.id) |> Ash.Query.load([:first_name_from_split]) - |> Api.read_one!() + |> Ash.read_one!() end end test "dependent calc" do post = Post - |> Ash.Changeset.new(%{title: "match", price: 10_024}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", price: 10_024}) + |> Ash.create!() Post.get_by_id(post.id, query: Post |> Ash.Query.select([:id]) |> Ash.Query.load([:price_string_with_currency_sign]) @@ -639,10 +643,14 @@ defmodule AshPostgres.CalculationTest do test "nested get_path works" do assert "thing" = Post - |> Ash.Changeset.new(%{title: "match", price: 10_024, stuff: %{foo: %{bar: "thing"}}}) + |> Ash.Changeset.for_create(:create, %{ + title: "match", + price: 10_024, + stuff: %{foo: %{bar: "thing"}} + }) |> Ash.Changeset.deselect(:stuff) - |> Api.create!() - |> Api.load!(:foo_bar_from_stuff) + |> Ash.create!() + |> Ash.load!(:foo_bar_from_stuff) |> Map.get(:foo_bar_from_stuff) end @@ -654,19 +662,19 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %AshPostgres.Test.Money{} = Post - |> Ash.Changeset.new(%{title: "match", price: 10_024}) + |> Ash.Changeset.for_create(:create, %{title: "match", price: 10_024}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() - |> Api.load!(:calc_returning_json) + |> Ash.create!() + |> Ash.load!(:calc_returning_json) |> Map.get(:calc_returning_json) assert [%AshPostgres.Test.Money{}] = author - |> Api.load!(posts: :calc_returning_json) + |> Ash.load!(posts: :calc_returning_json) |> Map.get(:posts) |> Enum.map(&Map.get(&1, :calc_returning_json)) end @@ -678,7 +686,7 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{calculations: %{length: 9}} = Author @@ -687,7 +695,7 @@ defmodule AshPostgres.CalculationTest do expr(string_length(string_trim(first_name <> last_name <> " "))), :integer ) - |> Api.read_one!() + |> Ash.read_one!() end test "an expression calculation that loads a runtime calculation works" do @@ -697,12 +705,12 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert [%{expr_referencing_runtime: "Bill Jones Bill Jones"}] = Author |> Ash.Query.load(:expr_referencing_runtime) - |> Api.read!() + |> Ash.read!() end test "lazy values are evaluated lazily" do @@ -712,7 +720,7 @@ defmodule AshPostgres.CalculationTest do last_name: "Jones", bio: %{title: "Mr.", bio: "Bones"} }) - |> Api.create!() + |> Ash.create!() assert %{calculations: %{string: "fred"}} = Author @@ -721,21 +729,21 @@ defmodule AshPostgres.CalculationTest do expr(lazy({__MODULE__, :fred, []})), :string ) - |> Api.read_one!() + |> Ash.read_one!() end test "exists with a relationship that has a filtered read action works" do post = Post |> Ash.Changeset.for_create(:create, %{}) - |> Api.create!() + |> Ash.create!() post_id = post.id assert [%{id: ^post_id}] = Post |> Ash.Query.filter(has_no_followers) - |> Api.read!() + |> Ash.read!() end def fred do diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 829f3a58..8e67e7e1 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -7,29 +7,29 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do certification = AshPostgres.Test.ComplexCalculations.Certification |> Ash.Changeset.new() - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() skill = AshPostgres.Test.ComplexCalculations.Skill |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:certification, certification, type: :append) - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() _documentation = AshPostgres.Test.ComplexCalculations.Documentation - |> Ash.Changeset.new(%{status: :demonstrated}) + |> Ash.Changeset.for_create(:create, %{status: :demonstrated}) |> Ash.Changeset.manage_relationship(:skill, skill, type: :append) - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() skill = skill - |> AshPostgres.Test.ComplexCalculations.Api.load!([:latest_documentation_status]) + |> Ash.load!([:latest_documentation_status]) assert skill.latest_documentation_status == :demonstrated certification = certification - |> AshPostgres.Test.ComplexCalculations.Api.load!([ + |> Ash.load!([ :count_of_skills ]) @@ -37,7 +37,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do certification = certification - |> AshPostgres.Test.ComplexCalculations.Api.load!([ + |> Ash.load!([ :count_of_approved_skills ]) @@ -45,7 +45,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do certification = certification - |> AshPostgres.Test.ComplexCalculations.Api.load!([ + |> Ash.load!([ :count_of_documented_skills ]) @@ -53,7 +53,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do certification = certification - |> AshPostgres.Test.ComplexCalculations.Api.load!([ + |> Ash.load!([ :count_of_documented_skills, :all_documentation_approved, :some_documentation_created @@ -66,35 +66,35 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do channel = AshPostgres.Test.ComplexCalculations.Channel |> Ash.Changeset.new() - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() user_1 = AshPostgres.Test.User |> Ash.Changeset.for_create(:create, %{name: "User 1"}) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() user_2 = AshPostgres.Test.User |> Ash.Changeset.for_create(:create, %{name: "User 2"}) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() channel_member_1 = AshPostgres.Test.ComplexCalculations.ChannelMember |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:channel, channel, type: :append) |> Ash.Changeset.manage_relationship(:user, user_1, type: :append) - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() channel_member_2 = AshPostgres.Test.ComplexCalculations.ChannelMember |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:channel, channel, type: :append) |> Ash.Changeset.manage_relationship(:user, user_2, type: :append) - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() channel = channel - |> AshPostgres.Test.ComplexCalculations.Api.load!([ + |> Ash.load!([ :first_member, :second_member ]) @@ -104,13 +104,13 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do channel = channel - |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_1) + |> Ash.load!(:name, actor: user_1) assert channel.name == user_1.name channel = channel - |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_2) + |> Ash.load!(:name, actor: user_2) assert channel.name == user_2.name end @@ -119,31 +119,31 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do dm_channel = AshPostgres.Test.ComplexCalculations.DMChannel |> Ash.Changeset.new() - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() user_1 = AshPostgres.Test.User |> Ash.Changeset.for_create(:create, %{name: "User 1"}) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() user_2 = AshPostgres.Test.User |> Ash.Changeset.for_create(:create, %{name: "User 2"}) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() channel_member_1 = AshPostgres.Test.ComplexCalculations.ChannelMember |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_1.id}) - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() channel_member_2 = AshPostgres.Test.ComplexCalculations.ChannelMember |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_2.id}) - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() dm_channel = dm_channel - |> AshPostgres.Test.ComplexCalculations.Api.load!([ + |> Ash.load!([ :first_member, :second_member ]) @@ -153,24 +153,24 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do dm_channel = dm_channel - |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_1) + |> Ash.load!(:name, actor: user_1) assert dm_channel.name == user_1.name dm_channel = dm_channel - |> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_2) + |> Ash.load!(:name, actor: user_2) assert dm_channel.name == user_2.name channels = AshPostgres.Test.ComplexCalculations.Channel |> Ash.Query.for_read(:read) - |> AshPostgres.Test.ComplexCalculations.Api.read!() + |> Ash.read!() channels = channels - |> AshPostgres.Test.ComplexCalculations.Api.load!([dm_channel: :name], + |> Ash.load!([dm_channel: :name], actor: user_1 ) @@ -180,7 +180,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do channel = channel - |> AshPostgres.Test.ComplexCalculations.Api.load!([:dm_name, :foo], actor: user_2) + |> Ash.load!([:dm_name, :foo], actor: user_2) assert channel.dm_name == user_2.name end @@ -188,92 +188,92 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do test "calculations with parent filters can be filtered on themselves" do AshPostgres.Test.ComplexCalculations.DMChannel |> Ash.Changeset.new() - |> AshPostgres.Test.ComplexCalculations.Api.create!() + |> Ash.create!() assert [%{foo: "foobar"}] = AshPostgres.Test.ComplexCalculations.Channel |> Ash.Query.filter(foo == "foobar") - |> AshPostgres.Test.ComplexCalculations.Api.read!(load: :foo) + |> Ash.read!(load: :foo) end test "calculations with aggregates can be referenced from aggregates" do author = AshPostgres.Test.Author - |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) - |> AshPostgres.Test.Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"}) + |> Ash.create!() AshPostgres.Test.Post - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() assert [%{author_count_of_posts: 1}] = AshPostgres.Test.Post |> Ash.Query.load(:author_count_of_posts) - |> AshPostgres.Test.Api.read!() + |> Ash.read!() assert [%{author_count_of_posts: 1}] = AshPostgres.Test.Post - |> AshPostgres.Test.Api.read!() - |> AshPostgres.Test.Api.load!(:author_count_of_posts) + |> Ash.read!() + |> Ash.load!(:author_count_of_posts) assert [_] = AshPostgres.Test.Post |> Ash.Query.filter(author_count_of_posts == 1) - |> AshPostgres.Test.Api.read!() + |> Ash.read!() end test "calculations can reference aggregates from optimizable first aggregates" do author = AshPostgres.Test.Author - |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) - |> AshPostgres.Test.Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"}) + |> Ash.create!() AshPostgres.Test.Post - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() assert [%{author_count_of_posts_agg: 1}] = AshPostgres.Test.Post |> Ash.Query.load(:author_count_of_posts_agg) - |> AshPostgres.Test.Api.read!() + |> Ash.read!() assert [%{author_count_of_posts_agg: 1}] = AshPostgres.Test.Post - |> AshPostgres.Test.Api.read!() - |> AshPostgres.Test.Api.load!(:author_count_of_posts_agg) + |> Ash.read!() + |> Ash.load!(:author_count_of_posts_agg) assert [_] = AshPostgres.Test.Post |> Ash.Query.filter(author_count_of_posts_agg == 1) - |> AshPostgres.Test.Api.read!() + |> Ash.read!() end test "calculations can reference aggregates from non optimizable aggregates" do author = AshPostgres.Test.Author - |> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) - |> AshPostgres.Test.Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"}) + |> Ash.create!() AshPostgres.Test.Post - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() assert [%{sum_of_author_count_of_posts: 1}] = AshPostgres.Test.Post |> Ash.Query.load(:sum_of_author_count_of_posts) - |> AshPostgres.Test.Api.read!() + |> Ash.read!() assert [%{sum_of_author_count_of_posts: 1}] = AshPostgres.Test.Post - |> AshPostgres.Test.Api.read!() - |> AshPostgres.Test.Api.load!(:sum_of_author_count_of_posts) + |> Ash.read!() + |> Ash.load!(:sum_of_author_count_of_posts) assert [_] = AshPostgres.Test.Post |> Ash.Query.filter(sum_of_author_count_of_posts == 1) - |> AshPostgres.Test.Api.read!() + |> Ash.read!() end end diff --git a/test/composite_type_test.exs b/test/composite_type_test.exs index 4f7e6a95..b1a3990c 100644 --- a/test/composite_type_test.exs +++ b/test/composite_type_test.exs @@ -1,13 +1,13 @@ defmodule AshPostgres.Test.CompositeTypeTest do use AshPostgres.RepoCase - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "can be cast and stored" do post = Post |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) - |> Api.create!() + |> Ash.create!() assert post.composite_point.x == 1 assert post.composite_point.y == 2 @@ -17,22 +17,22 @@ defmodule AshPostgres.Test.CompositeTypeTest do post = Post |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) - |> Api.create!() + |> Ash.create!() post_id = post.id - assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Api.read_one!() - refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Api.read_one!() + assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Ash.read_one!() + refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Ash.read_one!() end test "composite types can be constructed" do Post |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) - |> Api.create!() + |> Ash.create!() assert %{composite_origin: %{x: 0, y: 0}} = Post |> Ash.Query.load(:composite_origin) - |> Api.read_one!() + |> Ash.read_one!() end end diff --git a/test/constraint_test.exs b/test/constraint_test.exs index dd3b1872..b2ec9fb8 100644 --- a/test/constraint_test.exs +++ b/test/constraint_test.exs @@ -1,15 +1,15 @@ defmodule AshPostgres.ConstraintTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "constraint messages are properly raised" do assert_raise Ash.Error.Invalid, ~r/yo, bad price/, fn -> Post - |> Ash.Changeset.new(%{title: "title", price: -1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", price: -1}) + |> Ash.create!() end end end diff --git a/test/custom_index_test.exs b/test/custom_index_test.exs index 4c721a4c..1b021813 100644 --- a/test/custom_index_test.exs +++ b/test/custom_index_test.exs @@ -1,24 +1,28 @@ defmodule AshPostgres.Test.CustomIndexTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "unique constraint errors are properly caught" do Post - |> Ash.Changeset.new(%{title: "first", uniq_custom_one: "what", uniq_custom_two: "what2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{ + title: "first", + uniq_custom_one: "what", + uniq_custom_two: "what2" + }) + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/Invalid value provided for uniq_custom_one: dude what the heck/, fn -> Post - |> Ash.Changeset.new(%{ + |> Ash.Changeset.for_create(:create, %{ title: "first", uniq_custom_one: "what", uniq_custom_two: "what2" }) - |> Api.create!() + |> Ash.create!() end end end diff --git a/test/distinct_test.exs b/test/distinct_test.exs index 1f779973..bbc9a1c6 100644 --- a/test/distinct_test.exs +++ b/test/distinct_test.exs @@ -1,26 +1,26 @@ defmodule AshPostgres.DistinctTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query setup do Post - |> Ash.Changeset.new(%{title: "title", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", score: 1}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "title", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", score: 1}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "foo", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "foo", score: 2}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "foo", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "foo", score: 2}) + |> Ash.create!() :ok end @@ -30,7 +30,7 @@ defmodule AshPostgres.DistinctTest do Post |> Ash.Query.distinct(:title) |> Ash.Query.sort(:title) - |> Api.read!() + |> Ash.read!() assert [%{title: "foo"}, %{title: "title"}] = results end @@ -40,7 +40,7 @@ defmodule AshPostgres.DistinctTest do Post |> Ash.Query.distinct(:title) |> Ash.Query.sort(title: :desc) - |> Api.read!() + |> Ash.read!() assert [%{title: "title"}, %{title: "foo"}] = results end @@ -51,7 +51,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct(:title) |> Ash.Query.sort(id: :desc) |> Ash.Query.limit(3) - |> Api.read!() + |> Ash.read!() assert [_, _] = results end @@ -62,7 +62,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct(:title) |> Ash.Query.sort(id: :desc) |> Ash.Query.limit(3) - |> Api.read!() + |> Ash.read!() assert [_, _] = results end @@ -73,7 +73,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct(:title) |> Ash.Query.sort(id: :desc) |> Ash.Query.limit(1) - |> Api.read!() + |> Ash.read!() assert [_] = results end @@ -84,7 +84,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct(:negative_score) |> Ash.Query.sort(:negative_score) |> Ash.Query.load(:negative_score) - |> Api.read!() + |> Ash.read!() assert [ %{title: "foo", negative_score: -2}, @@ -96,7 +96,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct(:negative_score) |> Ash.Query.sort(negative_score: :desc) |> Ash.Query.load(:negative_score) - |> Api.read!() + |> Ash.read!() assert [ %{title: "title", negative_score: -1}, @@ -108,7 +108,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct(:negative_score) |> Ash.Query.sort(:title) |> Ash.Query.load(:negative_score) - |> Api.read!() + |> Ash.read!() assert [ %{title: "foo", negative_score: -2}, @@ -118,29 +118,29 @@ defmodule AshPostgres.DistinctTest do test "distinct, join filters and sort can be combined" do Post - |> Ash.Changeset.new(%{title: "a", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a", score: 2}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "a", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a", score: 1}) + |> Ash.create!() assert [] = Post |> Ash.Query.distinct(:negative_score) |> Ash.Query.filter(author.first_name == "a") |> Ash.Query.sort(:negative_score) - |> Api.read!() + |> Ash.read!() end test "distinct sort is applied" do Post - |> Ash.Changeset.new(%{title: "a", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a", score: 2}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "a", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a", score: 1}) + |> Ash.create!() results = Post @@ -148,7 +148,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct_sort(:title) |> Ash.Query.sort(:negative_score) |> Ash.Query.load(:negative_score) - |> Api.read!() + |> Ash.read!() assert [ %{title: "a", negative_score: -2}, @@ -161,7 +161,7 @@ defmodule AshPostgres.DistinctTest do |> Ash.Query.distinct_sort(title: :desc) |> Ash.Query.sort(:negative_score) |> Ash.Query.load(:negative_score) - |> Api.read!() + |> Ash.read!() assert [ %{title: "foo", negative_score: -2}, @@ -173,7 +173,7 @@ defmodule AshPostgres.DistinctTest do results = Post |> Ash.Query.distinct(:title) - |> Api.read!() + |> Ash.read!() assert [_, _] = results end diff --git a/test/embeddable_resource_test.exs b/test/embeddable_resource_test.exs index 2201acb3..4edb0903 100644 --- a/test/embeddable_resource_test.exs +++ b/test/embeddable_resource_test.exs @@ -1,34 +1,34 @@ defmodule AshPostgres.EmbeddableResourceTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Author, Bio, Post} + alias AshPostgres.Test.{Author, Bio, Post} require Ash.Query setup do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() %{post: post} end test "calculations can load json", %{post: post} do assert %{calc_returning_json: %AshPostgres.Test.Money{amount: 100, currency: :usd}} = - Api.load!(post, :calc_returning_json) + Ash.load!(post, :calc_returning_json) end test "embeds with list attributes set to nil are loaded as nil" do - post = + author = Author - |> Ash.Changeset.new(%{bio: %Bio{list_of_strings: nil}}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{bio: %Bio{list_of_strings: nil}}) + |> Ash.create!() - assert is_nil(post.bio.list_of_strings) + assert is_nil(author.bio.list_of_strings) - post = Api.reload!(post) + author = Ash.reload!(author) - assert is_nil(post.bio.list_of_strings) + assert is_nil(author.bio.list_of_strings) end end diff --git a/test/enum_test.exs b/test/enum_test.exs index 9ee180a4..537944c4 100644 --- a/test/enum_test.exs +++ b/test/enum_test.exs @@ -1,13 +1,13 @@ defmodule AshPostgres.EnumTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "valid values are properly inserted" do Post - |> Ash.Changeset.new(%{title: "title", status: :open}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", status: :open}) + |> Ash.create!() end end diff --git a/test/error_expr_test.exs b/test/error_expr_test.exs index 9e9b565b..a45c1a49 100644 --- a/test/error_expr_test.exs +++ b/test/error_expr_test.exs @@ -1,28 +1,28 @@ defmodule AshPostgres.ErrorExprTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query import Ash.Expr test "exceptions in filters are treated as regular Ash exceptions" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> Post |> Ash.Query.filter( error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10) ) - |> Api.read!() + |> Ash.read!() end end test "exceptions in calculations are treated as regular Ash exceptions" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> Post @@ -31,15 +31,15 @@ defmodule AshPostgres.ErrorExprTest do expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), :string ) - |> Api.read!() + |> Ash.read!() |> Enum.map(& &1.calculations) end end test "exceptions in calculations are treated as regular Ash exceptions in transactions" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> AshPostgres.TestRepo.transaction!(fn -> @@ -49,7 +49,7 @@ defmodule AshPostgres.ErrorExprTest do expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), :string ) - |> Api.read!() + |> Ash.read!() |> Enum.map(& &1.calculations) end) end diff --git a/test/filter_field_policy_test.exs b/test/filter_field_policy_test.exs index 35b29e5e..a69f2429 100644 --- a/test/filter_field_policy_test.exs +++ b/test/filter_field_policy_test.exs @@ -1,7 +1,7 @@ defmodule FilterFieldPolicyTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Organization, Post, User} + alias AshPostgres.Test.{Organization, Post, User} require Ash.Query @@ -9,15 +9,15 @@ defmodule FilterFieldPolicyTest do organization = Organization |> Ash.Changeset.for_create(:create, %{name: "test_org"}) - |> Api.create!() + |> Ash.create!() User |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) - |> Api.create!() + |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) - |> Api.create!() + |> Ash.create!() filter = Ash.Filter.parse_input!(Post, %{organization: %{name: %{ilike: "%org"}}}) @@ -25,7 +25,7 @@ defmodule FilterFieldPolicyTest do Post |> Ash.Query.do_filter(filter) |> Ash.Query.for_read(:allow_any) - |> Api.read!(actor: %{id: "test"}) + |> Ash.read!(actor: %{id: "test"}) filter = Ash.Filter.parse_input!(Post, %{organization: %{users: %{name: %{ilike: "%bar"}}}}) @@ -33,6 +33,6 @@ defmodule FilterFieldPolicyTest do Post |> Ash.Query.do_filter(filter) |> Ash.Query.for_read(:allow_any) - |> Api.read!(actor: %{id: "test"}) + |> Ash.read!(actor: %{id: "test"}) end end diff --git a/test/filter_test.exs b/test/filter_test.exs index bc535bd0..199c35ae 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,20 +1,21 @@ defmodule AshPostgres.FilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Author, Comment, Post} + alias AshPostgres.Test.{Author, Comment, Post} + alias AshPostgres.Test.ComplexCalculations.{Channel, ChannelMember} require Ash.Query describe "with no filter applied" do test "with no data" do - assert [] = Api.read!(Post) + assert [] = Ash.read!(Post) end test "with data" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() - assert [%Post{title: "title"}] = Api.read!(Post) + assert [%Post{title: "title"}] = Ash.read!(Post) end end @@ -23,7 +24,7 @@ defmodule AshPostgres.FilterTest do assert_raise Ash.Error.Invalid, fn -> Post |> Ash.Query.filter(id == "foo") - |> Api.read!() + |> Ash.read!() end end end @@ -41,7 +42,7 @@ defmodule AshPostgres.FilterTest do assert_raise Ash.Error.Query.InvalidExpression, fn -> Post |> Ash.Query.filter(category == "blah") - |> Api.read!() + |> Ash.read!() end end end @@ -51,33 +52,33 @@ defmodule AshPostgres.FilterTest do results = Post |> Ash.Query.filter(title == "title") - |> Api.read!() + |> Ash.read!() assert [] = results end test "with data that matches" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() results = Post |> Ash.Query.filter(title == "title") - |> Api.read!() + |> Ash.read!() assert [%Post{title: "title"}] = results end test "with some data that matches and some data that doesnt" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() results = Post |> Ash.Query.filter(title == "no_title") - |> Api.read!() + |> Ash.read!() assert [] = results end @@ -85,18 +86,18 @@ defmodule AshPostgres.FilterTest do test "with related data that doesn't match" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "not match"}) + |> Ash.Changeset.for_create(:create, %{title: "not match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.filter(comments.title == "match") - |> Api.read!() + |> Ash.read!() assert [] = results end @@ -104,31 +105,31 @@ defmodule AshPostgres.FilterTest do test "with related data two steps away that matches" do author = Author - |> Ash.Changeset.new(%{first_name: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{first_name: "match"}) + |> Ash.create!() post = Post - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "not match"}) + |> Ash.Changeset.for_create(:create, %{title: "not match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Comment |> Ash.Query.filter(author.posts.linked_posts.title == "title") - |> Api.read!() + |> Ash.read!() assert [_] = results end @@ -136,18 +137,18 @@ defmodule AshPostgres.FilterTest do test "with related data that does match" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.filter(comments.title == "match") - |> Api.read!() + |> Ash.read!() assert [%Post{title: "title"}] = results end @@ -155,23 +156,23 @@ defmodule AshPostgres.FilterTest do test "with related data that does and doesn't match" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "not match"}) + |> Ash.Changeset.for_create(:create, %{title: "not match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.filter(comments.title == "match") - |> Api.read!() + |> Ash.read!() assert [%Post{title: "title"}] = results end @@ -180,22 +181,22 @@ defmodule AshPostgres.FilterTest do describe "in" do test "it properly filters" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "title1"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title1"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() assert [%Post{title: "title1"}, %Post{title: "title2"}] = Post |> Ash.Query.filter(title in ["title1", "title2"]) |> Ash.Query.sort(title: :asc) - |> Api.read!() + |> Ash.read!() end end @@ -204,37 +205,37 @@ defmodule AshPostgres.FilterTest do results = Post |> Ash.Query.filter(title == "title" or score == 1) - |> Api.read!() + |> Ash.read!() assert [] = results end test "with data that doesn't match" do Post - |> Ash.Changeset.new(%{title: "no title", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "no title", score: 2}) + |> Ash.create!() results = Post |> Ash.Query.filter(title == "title" or score == 1) - |> Api.read!() + |> Ash.read!() assert [] = results end test "with data that matches both conditions" do Post - |> Ash.Changeset.new(%{title: "title", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", score: 0}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{score: 1, title: "nothing"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{score: 1, title: "nothing"}) + |> Ash.create!() results = Post |> Ash.Query.filter(title == "title" or score == 1) - |> Api.read!() + |> Ash.read!() |> Enum.sort_by(& &1.score) assert [%Post{title: "title", score: 0}, %Post{title: "nothing", score: 1}] = results @@ -242,17 +243,17 @@ defmodule AshPostgres.FilterTest do test "with data that matches one condition and data that matches nothing" do Post - |> Ash.Changeset.new(%{title: "title", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", score: 0}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{score: 2, title: "nothing"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{score: 2, title: "nothing"}) + |> Ash.create!() results = Post |> Ash.Query.filter(title == "title" or score == 1) - |> Api.read!() + |> Ash.read!() |> Enum.sort_by(& &1.score) assert [%Post{title: "title", score: 0}] = results @@ -261,18 +262,18 @@ defmodule AshPostgres.FilterTest do test "with related data in an or statement that matches, while basic filter doesn't match" do post = Post - |> Ash.Changeset.new(%{title: "doesn't match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "doesn't match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.filter(title == "match" or comments.title == "match") - |> Api.read!() + |> Ash.read!() assert [%Post{title: "doesn't match"}] = results end @@ -280,18 +281,18 @@ defmodule AshPostgres.FilterTest do test "with related data in an or statement that doesn't match, while basic filter does match" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "doesn't match"}) + |> Ash.Changeset.for_create(:create, %{title: "doesn't match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.filter(title == "match" or comments.title == "match") - |> Api.read!() + |> Ash.read!() assert [%Post{title: "match"}] = results end @@ -299,25 +300,25 @@ defmodule AshPostgres.FilterTest do test "with related data and an inner join condition" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.filter(title == comments.title) - |> Api.read!() + |> Ash.read!() assert [%Post{title: "match"}] = results results = Post |> Ash.Query.filter(title != comments.title) - |> Api.read!() + |> Ash.read!() assert [] = results end @@ -329,13 +330,13 @@ defmodule AshPostgres.FilterTest do |> Ash.Changeset.for_create(:create, bio: %{title: "Dr.", bio: "Strange", years_of_experience: 10} ) - |> Api.create!() + |> Ash.create!() Author |> Ash.Changeset.for_create(:create, bio: %{title: "Highlander", bio: "There can be only one."} ) - |> Api.create!() + |> Ash.create!() :ok end @@ -344,151 +345,151 @@ defmodule AshPostgres.FilterTest do assert [%{bio: %{title: "Dr."}}] = Author |> Ash.Query.filter(bio[:title] == "Dr.") - |> Api.read!() + |> Ash.read!() end test "works using simple equality for integers" do assert [%{bio: %{title: "Dr."}}] = Author |> Ash.Query.filter(bio[:years_of_experience] == 10) - |> Api.read!() + |> Ash.read!() end test "works using an expression" do assert [%{bio: %{title: "Highlander"}}] = Author |> Ash.Query.filter(contains(type(bio[:bio], :string), "only one.")) - |> Api.read!() + |> Ash.read!() end test "calculations that use embeds can be filtered on" do assert [%{bio: %{title: "Dr."}}] = Author |> Ash.Query.filter(title == "Dr.") - |> Api.read!() + |> Ash.read!() end end describe "basic expressions" do test "basic expressions work" do Post - |> Ash.Changeset.new(%{title: "match", score: 4}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", score: 4}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "non_match", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "non_match", score: 2}) + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(score + 1 == 5) - |> Api.read!() + |> Ash.read!() end end describe "case insensitive fields" do test "it matches case insensitively" do Post - |> Ash.Changeset.new(%{title: "match", category: "FoObAr"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", category: "FoObAr"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{category: "bazbuz"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{category: "bazbuz"}) + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(category == "fOoBaR") - |> Api.read!() + |> Ash.read!() end end describe "contains/2" do test "it works when it matches" do Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "bazbuz"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bazbuz"}) + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(contains(title, "atc")) - |> Api.read!() + |> Ash.read!() end test "it works when a case insensitive string is provided as a value" do Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "bazbuz"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bazbuz"}) + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(contains(title, ^%Ash.CiString{string: "ATC"})) - |> Api.read!() + |> Ash.read!() end test "it works on a case insensitive column" do Post - |> Ash.Changeset.new(%{category: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{category: "match"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{category: "bazbuz"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{category: "bazbuz"}) + |> Ash.create!() assert [%{category: %Ash.CiString{string: "match"}}] = Post |> Ash.Query.filter(contains(category, ^"ATC")) - |> Api.read!() + |> Ash.read!() end test "it works on a case insensitive calculation" do Post - |> Ash.Changeset.new(%{category: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{category: "match"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{category: "bazbuz"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{category: "bazbuz"}) + |> Ash.create!() assert [%{category: %Ash.CiString{string: "match"}}] = Post |> Ash.Query.filter(contains(category_label, ^"ATC")) - |> Api.read!() + |> Ash.read!() end test "it works on related values" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "abba"}) + |> Ash.Changeset.for_create(:create, %{title: "abba"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "no_match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "no_match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "acca"}) + |> Ash.Changeset.for_create(:create, %{title: "acca"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(contains(comments.title, ^"bb")) - |> Api.read!() + |> Ash.read!() end end @@ -496,46 +497,46 @@ defmodule AshPostgres.FilterTest do test "it works with a list attribute" do author1 = Author - |> Ash.Changeset.new(%{badges: [:author_of_the_year]}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{badges: [:author_of_the_year]}) + |> Ash.create!() _author2 = Author - |> Ash.Changeset.new(%{badges: []}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{badges: []}) + |> Ash.create!() author1_id = author1.id assert [%{id: ^author1_id}] = Author |> Ash.Query.filter(length(badges) > 0) - |> Api.read!() + |> Ash.read!() end test "it works with nil" do author1 = Author - |> Ash.Changeset.new(%{badges: [:author_of_the_year]}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{badges: [:author_of_the_year]}) + |> Ash.create!() _author2 = Author |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() author1_id = author1.id assert [%{id: ^author1_id}] = Author |> Ash.Query.filter(length(badges || []) > 0) - |> Api.read!() + |> Ash.read!() end test "it works with a list" do author1 = Author |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() author1_id = author1.id @@ -544,23 +545,23 @@ defmodule AshPostgres.FilterTest do assert [%{id: ^author1_id}] = Author |> Ash.Query.filter(length(^explicit_list) > 0) - |> Api.read!() + |> Ash.read!() assert [] = Author |> Ash.Query.filter(length(^explicit_list) > 1) - |> Api.read!() + |> Ash.read!() end test "it raises with bad values" do Author |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() assert_raise(Ash.Error.Unknown, fn -> Author |> Ash.Query.filter(length(first_name) > 0) - |> Api.read!() + |> Ash.read!() end) end end @@ -569,116 +570,116 @@ defmodule AshPostgres.FilterTest do test "it works with single relationships" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "abba"}) + |> Ash.Changeset.for_create(:create, %{title: "abba"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "no_match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "no_match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "acca"}) + |> Ash.Changeset.for_create(:create, %{title: "acca"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(exists(comments, title == ^"abba")) - |> Api.read!() + |> Ash.read!() end test "it works with many to many relationships" do post = Post - |> Ash.Changeset.new(%{title: "a"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "b"}] = Post |> Ash.Query.filter(exists(linked_posts, title == ^"a")) - |> Api.read!() + |> Ash.read!() end test "it works with join association relationships" do post = Post - |> Ash.Changeset.new(%{title: "a"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "b"}] = Post |> Ash.Query.filter(exists(linked_posts, title == ^"a")) - |> Api.read!() + |> Ash.read!() end test "it works with nested relationships as the path" do post = Post - |> Ash.Changeset.new(%{title: "a"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.for_create(:create, %{title: "comment"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "b"}] = Post |> Ash.Query.filter(exists(linked_posts.comments, title == ^"comment")) - |> Api.read!() + |> Ash.read!() end test "it works with an `at_path`" do post = Post - |> Ash.Changeset.new(%{title: "a"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() other_post = Post - |> Ash.Changeset.new(%{title: "other_a"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "other_a"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.for_create(:create, %{title: "comment"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.for_create(:create, %{title: "comment"}) |> Ash.Changeset.manage_relationship(:post, other_post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [other_post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "b"}] = Post @@ -686,7 +687,7 @@ defmodule AshPostgres.FilterTest do linked_posts.title == "a" and linked_posts.exists(comments, title == ^"comment") ) - |> Api.read!() + |> Ash.read!() assert [%{title: "b"}] = Post @@ -694,90 +695,90 @@ defmodule AshPostgres.FilterTest do linked_posts.title == "a" and linked_posts.exists(comments, title == ^"comment") ) - |> Api.read!() + |> Ash.read!() end test "it works with nested relationships inside of exists" do post = Post - |> Ash.Changeset.new(%{title: "a"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.for_create(:create, %{title: "comment"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "b"}] = Post |> Ash.Query.filter(exists(linked_posts, comments.title == ^"comment")) - |> Api.read!() + |> Ash.read!() end test "it works with aggregates inside of exists" do post = Post - |> Ash.Changeset.new(%{title: "a", category: "foo"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "a", category: "foo"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "comment"}) + |> Ash.Changeset.for_create(:create, %{title: "comment"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "b"}) + |> Ash.Changeset.for_create(:create, %{title: "b"}) |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{title: "b"}] = Post |> Ash.Query.filter( exists(linked_posts.comments, title == ^"comment" and post_category == "foo") ) - |> Api.read!() + |> Ash.read!() end end describe "filtering on enum types" do test "it allows simple filtering" do Post - |> Ash.Changeset.new(status_enum: "open") - |> Api.create!() + |> Ash.Changeset.for_create(:create, status_enum: "open") + |> Ash.create!() assert %{status_enum: :open} = Post |> Ash.Query.filter(status_enum == ^"open") - |> Api.read_one!() + |> Ash.read_one!() end test "it allows simple filtering without casting" do Post - |> Ash.Changeset.new(status_enum_no_cast: "open") - |> Api.create!() + |> Ash.Changeset.for_create(:create, status_enum_no_cast: "open") + |> Ash.create!() assert %{status_enum_no_cast: :open} = Post |> Ash.Query.filter(status_enum_no_cast == ^"open") - |> Api.read_one!() + |> Ash.read_one!() end end describe "atom filters" do test "it works on matches" do Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() result = Post |> Ash.Query.filter(type == :sponsored) - |> Api.read!() + |> Ash.read!() assert [%Post{title: "match"}] = result end @@ -786,40 +787,40 @@ defmodule AshPostgres.FilterTest do describe "like and ilike" do test "like builds and matches" do Post - |> Ash.Changeset.new(%{title: "MaTcH"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "MaTcH"}) + |> Ash.create!() results = Post |> Ash.Query.filter(like(title, "%aTc%")) - |> Api.read!() + |> Ash.read!() assert [%Post{title: "MaTcH"}] = results results = Post |> Ash.Query.filter(like(title, "%atc%")) - |> Api.read!() + |> Ash.read!() assert [] = results end test "ilike builds and matches" do Post - |> Ash.Changeset.new(%{title: "MaTcH"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "MaTcH"}) + |> Ash.create!() results = Post |> Ash.Query.filter(ilike(title, "%aTc%")) - |> Api.read!() + |> Ash.read!() assert [%Post{title: "MaTcH"}] = results results = Post |> Ash.Query.filter(ilike(title, "%atc%")) - |> Api.read!() + |> Ash.read!() assert [%Post{title: "MaTcH"}] = results end @@ -828,26 +829,26 @@ defmodule AshPostgres.FilterTest do describe "trigram_similarity" do test "it works on matches" do Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() results = Post |> Ash.Query.filter(trigram_similarity(title, "match") > 0.9) - |> Api.read!() + |> Ash.read!() assert [%Post{title: "match"}] = results end test "it works on non-matches" do Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() results = Post |> Ash.Query.filter(trigram_similarity(title, "match") < 0.1) - |> Api.read!() + |> Ash.read!() assert [] = results end @@ -857,139 +858,136 @@ defmodule AshPostgres.FilterTest do test "double replacement works" do post = Post - |> Ash.Changeset.new(%{title: "match", score: 4}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match", score: 4}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "non_match", score: 2}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "non_match", score: 2}) + |> Ash.create!() assert [%{title: "match"}] = Post |> Ash.Query.filter(fragment("? = ?", title, ^post.title)) - |> Api.read!() + |> Ash.read!() assert [] = Post |> Ash.Query.filter(fragment("? = ?", title, "nope")) - |> Api.read!() + |> Ash.read!() end end test "filtering allows filtering on list aggregates with filters" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() post_id = post.id Comment - |> Ash.Changeset.new(%{title: "match", likes: 5}) + |> Ash.Changeset.for_create(:create, %{title: "match", likes: 5}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non_match", likes: 5}) + |> Ash.Changeset.for_create(:create, %{title: "non_match", likes: 5}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "non_match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "non_match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "non_match", likes: 5}) + |> Ash.Changeset.for_create(:create, %{title: "non_match", likes: 5}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{id: ^post_id}] = Post |> Ash.Query.filter("match" in comment_titles_with_5_likes) - |> Api.read!() + |> Ash.read!() end test "filtering allows filtering on count aggregates with filters" do post = Post - |> Ash.Changeset.new(%{title: "match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() post_id = post.id Comment - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "non_match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "non_match"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title"}) + |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{id: ^post_id}] = Post |> Ash.Query.filter(count_of_comments_that_have_a_post == 2) - |> Api.read!() + |> Ash.read!() end describe "filtering on relationships that themselves have filters" do test "it doesn't raise an error" do Comment |> Ash.Query.filter(not is_nil(popular_ratings.id)) - |> Api.read!() + |> Ash.read!() end test "it doesn't raise an error when nested" do Post |> Ash.Query.filter(not is_nil(comments.popular_ratings.id)) - |> Api.read!() + |> Ash.read!() end test "aggregates using them don't raise errors" do Comment |> Ash.Query.load(:co_popular_comments) - |> Api.read!() + |> Ash.read!() end test "filtering on aggregates using them doesn't raise errors" do Comment |> Ash.Query.filter(co_popular_comments > 1) - |> Api.read!() + |> Ash.read!() end end test "can reference related items from a relationship expression" do Post |> Ash.Query.filter(comments_with_high_rating.title == "foo") - |> Api.read!() + |> Ash.read!() end test "filter by has_one from_many?" do - alias Ash.Changeset - alias AshPostgres.Test.ComplexCalculations.{Api, Channel, ChannelMember} - [_cm1, cm2 | _] = for _ <- 1..5 do - c = Changeset.for_create(Channel, :create, %{}) |> Api.create!() - Changeset.for_create(ChannelMember, :create, %{channel_id: c.id}) |> Api.create!() + c = Ash.Changeset.for_create(Channel, :create, %{}) |> Ash.create!() + Ash.Changeset.for_create(ChannelMember, :create, %{channel_id: c.id}) |> Ash.create!() end assert Channel |> Ash.Query.for_read(:read) |> Ash.Query.filter(first_member.id != ^cm2.id) - |> Api.read!() + |> Ash.read!() |> length == 4 end end diff --git a/test/load_test.exs b/test/load_test.exs index f01e95bb..079a2141 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Comment, Post, Record, TempEntity} + alias AshPostgres.Test.{Comment, Post, Record, TempEntity} require Ash.Query @@ -8,18 +8,18 @@ defmodule AshPostgres.Test.LoadTest do assert %Post{comments: %Ash.NotLoaded{type: :relationship}} = post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Post |> Ash.Query.load(:comments) - |> Api.read!() + |> Ash.read!() assert [%Post{comments: [%{title: "match"}]}] = results end @@ -28,18 +28,18 @@ defmodule AshPostgres.Test.LoadTest do assert %Comment{post: %Ash.NotLoaded{type: :relationship}} = comment = Comment - |> Ash.Changeset.new(%{}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "match"}) + |> Ash.Changeset.for_create(:create, %{title: "match"}) |> Ash.Changeset.manage_relationship(:comments, [comment], type: :append_and_remove) - |> Api.create!() + |> Ash.create!() results = Comment |> Ash.Query.load(:post) - |> Api.read!() + |> Ash.read!() assert [%Comment{post: %{title: "match"}}] = results end @@ -47,29 +47,29 @@ defmodule AshPostgres.Test.LoadTest do test "many_to_many loads work" do source_post = Post - |> Ash.Changeset.new(%{title: "source"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "source"}) + |> Ash.create!() destination_post = Post - |> Ash.Changeset.new(%{title: "destination"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "destination"}) + |> Ash.create!() destination_post2 = Post - |> Ash.Changeset.new(%{title: "destination"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "destination"}) + |> Ash.create!() source_post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], type: :append_and_remove ) - |> Api.update!() + |> Ash.update!() results = source_post - |> Api.load!(:linked_posts) + |> Ash.load!(:linked_posts) assert %{linked_posts: [%{title: "destination"}, %{title: "destination"}]} = results end @@ -77,29 +77,29 @@ defmodule AshPostgres.Test.LoadTest do test "many_to_many loads work when nested" do source_post = Post - |> Ash.Changeset.new(%{title: "source"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "source"}) + |> Ash.create!() destination_post = Post - |> Ash.Changeset.new(%{title: "destination"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "destination"}) + |> Ash.create!() source_post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post], type: :append_and_remove ) - |> Api.update!() + |> Ash.update!() destination_post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [source_post], type: :append_and_remove) - |> Api.update!() + |> Ash.update!() results = source_post - |> Api.load!(linked_posts: :linked_posts) + |> Ash.load!(linked_posts: :linked_posts) assert %{linked_posts: [%{title: "destination", linked_posts: [%{title: "source"}]}]} = results @@ -109,75 +109,75 @@ defmodule AshPostgres.Test.LoadTest do test "parent references are resolved" do post1 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() post2_id = post2.id post3 = Post - |> Ash.Changeset.new(%{title: "no match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "no match"}) + |> Ash.create!() assert [%{posts_with_matching_title: [%{id: ^post2_id}]}] = Post |> Ash.Query.load(:posts_with_matching_title) |> Ash.Query.filter(id == ^post1.id) - |> Api.read!() + |> Ash.read!() assert [%{posts_with_matching_title: []}] = Post |> Ash.Query.load(:posts_with_matching_title) |> Ash.Query.filter(id == ^post3.id) - |> Api.read!() + |> Ash.read!() end test "parent references work when joining for filters" do %{id: post1_id} = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "no match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "no match"}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "no match"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "no match"}) + |> Ash.create!() assert [%{id: ^post1_id}] = Post |> Ash.Query.filter(posts_with_matching_title.id == ^post2.id) - |> Api.read!() + |> Ash.read!() end test "lateral join loads (loads with limits or offsets) are supported" do assert %Post{comments: %Ash.NotLoaded{type: :relationship}} = post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "abc"}) + |> Ash.Changeset.for_create(:create, %{title: "abc"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "def"}) + |> Ash.Changeset.for_create(:create, %{title: "def"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() comments_query = Comment @@ -187,7 +187,7 @@ defmodule AshPostgres.Test.LoadTest do results = Post |> Ash.Query.load(comments: comments_query) - |> Api.read!() + |> Ash.read!() assert [%Post{comments: [%{title: "abc"}]}] = results @@ -199,7 +199,7 @@ defmodule AshPostgres.Test.LoadTest do results = Post |> Ash.Query.load(comments: comments_query) - |> Api.read!() + |> Ash.read!() assert [%Post{comments: [%{title: "def"}]}] = results @@ -211,7 +211,7 @@ defmodule AshPostgres.Test.LoadTest do results = Post |> Ash.Query.load(comments: comments_query) - |> Api.read!() + |> Ash.read!() assert [%Post{comments: [%{title: "def"}, %{title: "abc"}]}] = results end @@ -219,25 +219,25 @@ defmodule AshPostgres.Test.LoadTest do test "loading many to many relationships on records works without loading its join relationship when using code interface" do source_post = Post - |> Ash.Changeset.new(%{title: "source"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "source"}) + |> Ash.create!() destination_post = Post - |> Ash.Changeset.new(%{title: "abc"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "abc"}) + |> Ash.create!() destination_post2 = Post - |> Ash.Changeset.new(%{title: "def"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "def"}) + |> Ash.create!() source_post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], type: :append_and_remove ) - |> Api.update!() + |> Ash.update!() assert %{linked_posts: [_, _]} = Post.get_by_id!(source_post.id, load: [:linked_posts]) end @@ -245,25 +245,25 @@ defmodule AshPostgres.Test.LoadTest do test "lateral join loads with many to many relationships are supported" do source_post = Post - |> Ash.Changeset.new(%{title: "source"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "source"}) + |> Ash.create!() destination_post = Post - |> Ash.Changeset.new(%{title: "abc"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "abc"}) + |> Ash.create!() destination_post2 = Post - |> Ash.Changeset.new(%{title: "def"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "def"}) + |> Ash.create!() source_post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], type: :append_and_remove ) - |> Api.update!() + |> Ash.update!() linked_posts_query = Post @@ -272,7 +272,7 @@ defmodule AshPostgres.Test.LoadTest do results = source_post - |> Api.load!(linked_posts: linked_posts_query) + |> Ash.load!(linked_posts: linked_posts_query) assert %{linked_posts: [%{title: "abc"}]} = results @@ -283,7 +283,7 @@ defmodule AshPostgres.Test.LoadTest do results = source_post - |> Api.load!(linked_posts: linked_posts_query) + |> Ash.load!(linked_posts: linked_posts_query) assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results end @@ -291,25 +291,25 @@ defmodule AshPostgres.Test.LoadTest do test "lateral join loads with many to many relationships are supported with aggregates" do source_post = Post - |> Ash.Changeset.new(%{title: "source"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "source"}) + |> Ash.create!() destination_post = Post - |> Ash.Changeset.new(%{title: "abc"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "abc"}) + |> Ash.create!() destination_post2 = Post - |> Ash.Changeset.new(%{title: "def"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "def"}) + |> Ash.create!() source_post |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], type: :append_and_remove ) - |> Api.update!() + |> Ash.update!() linked_posts_query = Post @@ -318,7 +318,7 @@ defmodule AshPostgres.Test.LoadTest do results = source_post - |> Api.load!(linked_posts: linked_posts_query) + |> Ash.load!(linked_posts: linked_posts_query) assert %{linked_posts: [%{title: "abc"}]} = results @@ -330,16 +330,18 @@ defmodule AshPostgres.Test.LoadTest do results = source_post - |> Api.load!(linked_posts: linked_posts_query) + |> Ash.load!(linked_posts: linked_posts_query) assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results end test "lateral join loads with read action from a custom table and schema" do - record = Record |> Ash.Changeset.new(%{full_name: "name"}) |> Api.create!() - temp_entity = TempEntity |> Ash.Changeset.new(%{full_name: "name"}) |> Api.create!() + record = Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() - assert %{entity: entity} = Api.load!(record, :entity) + temp_entity = + TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + + assert %{entity: entity} = Ash.load!(record, :entity) assert temp_entity.id == entity.id end diff --git a/test/lock_test.exs b/test/lock_test.exs index 36dd97fd..f36c8ed2 100644 --- a/test/lock_test.exs +++ b/test/lock_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LockTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query setup do @@ -17,7 +17,7 @@ defmodule AshPostgres.Test.LockTest do Post |> Ash.Changeset.for_create(:create, %{title: "locked"}) |> Ash.Changeset.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}}) - |> Api.create!() + |> Ash.create!() task1 = Task.async(fn -> @@ -26,7 +26,7 @@ defmodule AshPostgres.Test.LockTest do |> Ash.Query.lock("FOR UPDATE NOWAIT") |> Ash.Query.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}}) |> Ash.Query.filter(id == ^post.id) - |> Api.read!() + |> Ash.read!() :timer.sleep(1000) :ok @@ -43,7 +43,7 @@ defmodule AshPostgres.Test.LockTest do |> Ash.Query.lock("FOR UPDATE NOWAIT") |> Ash.Query.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}}) |> Ash.Query.filter(id == ^post.id) - |> Api.read!() + |> Ash.read!() end) rescue e -> diff --git a/test/manual_relationships_test.exs b/test/manual_relationships_test.exs index 7b1b8d23..0783c94b 100644 --- a/test/manual_relationships_test.exs +++ b/test/manual_relationships_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Comment, Post} + alias AshPostgres.Test.{Comment, Post} require Ash.Query @@ -8,102 +8,102 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do test "aggregates can be loaded with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert %{count_of_comments_containing_title: 0} = - Api.load!(post, :count_of_comments_containing_title) + Ash.load!(post, :count_of_comments_containing_title) end test "aggregates can be loaded with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{count_of_comments_containing_title: 1} = - Api.load!(post, :count_of_comments_containing_title) + Ash.load!(post, :count_of_comments_containing_title) end test "relationships can be filtered on with no data" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert [] = - Post |> Ash.Query.filter(comments_containing_title.title == "title") |> Api.read!() + Post |> Ash.Query.filter(comments_containing_title.title == "title") |> Ash.read!() end test "aggregates can be filtered on with no data" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() - assert [] = Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Api.read!() + assert [] = Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Ash.read!() end test "aggregates can be filtered on with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [_] = - Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Api.read!() + Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Ash.read!() end test "relationships can be filtered on with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [_] = Post |> Ash.Query.filter(comments_containing_title.title == "title2") - |> Api.read!() + |> Ash.read!() end end @@ -111,128 +111,128 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do test "aggregates can be loaded with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() comment = Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{count_of_comments_containing_title: 0} = - Api.load!(comment, :count_of_comments_containing_title) + Ash.load!(comment, :count_of_comments_containing_title) end test "aggregates can be loaded with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() comment = Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{count_of_comments_containing_title: 1} = - Api.load!(comment, :count_of_comments_containing_title) + Ash.load!(comment, :count_of_comments_containing_title) end test "aggregates can be filtered on with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [] = Comment |> Ash.Query.filter(count_of_comments_containing_title == 1) - |> Api.read!() + |> Ash.read!() end test "relationships can be filtered on with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [] = Comment |> Ash.Query.filter(post.comments_containing_title.title == "title2") - |> Api.read!() + |> Ash.read!() end test "aggregates can be filtered on with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [_, _] = Comment |> Ash.Query.filter(count_of_comments_containing_title == 1) - |> Api.read!() + |> Ash.read!() end test "relationships can be filtered on with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [_, _] = Comment |> Ash.Query.filter(post.comments_containing_title.title == "title2") - |> Api.read!() + |> Ash.read!() end end @@ -240,128 +240,128 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do test "aggregates can be loaded with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() comment = Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{posts_for_comments_containing_title: []} = - Api.load!(comment, :posts_for_comments_containing_title) + Ash.load!(comment, :posts_for_comments_containing_title) end test "aggregates can be loaded with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() comment = Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert %{posts_for_comments_containing_title: ["title"]} = - Api.load!(comment, :posts_for_comments_containing_title) + Ash.load!(comment, :posts_for_comments_containing_title) end test "aggregates can be filtered on with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [] = Comment |> Ash.Query.filter("title" in posts_for_comments_containing_title) - |> Api.read!() + |> Ash.read!() end test "relationships can be filtered on with no data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [] = Comment |> Ash.Query.filter(post.comments_containing_title.post.title == "title") - |> Api.read!() + |> Ash.read!() end test "aggregates can be filtered on with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [_, _] = Comment |> Ash.Query.filter(post.comments_containing_title.post.title == "title") - |> Api.read!() + |> Ash.read!() end test "relationships can be filtered on with data" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "title2"}) + |> Ash.Changeset.for_create(:create, %{title: "title2"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "no match"}) + |> Ash.Changeset.for_create(:create, %{title: "no match"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [_, _] = Comment |> Ash.Query.filter(post.comments_containing_title.post.title == "title") - |> Api.read!() + |> Ash.read!() end end end diff --git a/test/manual_update_test.exs b/test/manual_update_test.exs index c41445e1..e5a286cf 100644 --- a/test/manual_update_test.exs +++ b/test/manual_update_test.exs @@ -4,18 +4,18 @@ defmodule AshPostgres.ManualUpdateTest do test "Manual update defined in a module to update an attribute" do post = AshPostgres.Test.Post - |> Ash.Changeset.new(%{title: "match"}) - |> AshPostgres.Test.Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() AshPostgres.Test.Comment - |> Ash.Changeset.new(%{title: "_"}) + |> Ash.Changeset.for_create(:create, %{title: "_"}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> AshPostgres.Test.Api.create!() + |> Ash.create!() post = post |> Ash.Changeset.for_update(:manual_update) - |> AshPostgres.Test.Api.update!() + |> Ash.update!() assert post.title == "manual" diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index eb0810c1..751c24f1 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -10,6 +10,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defmodule unquote(mod) do use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer postgres do @@ -34,28 +35,20 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defmacrop defapi(resources) do + defmacrop defdomain(resources) do quote do Code.compiler_options(ignore_module_conflict: true) - defmodule Registry do - use Ash.Registry + defmodule Domain do + use Ash.Domain - entries do + resources do for resource <- unquote(resources) do - entry(resource) + resource(resource) end end end - defmodule Api do - use Ash.Api - - resources do - registry(Registry) - end - end - Code.compiler_options(ignore_module_conflict: false) end end @@ -65,7 +58,7 @@ defmodule AshPostgres.MigrationGeneratorTest do Code.compiler_options(ignore_module_conflict: true) defmodule unquote(mod) do - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, data_layer: AshPostgres.DataLayer, domain: nil postgres do table unquote(table) @@ -104,19 +97,19 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:second_title, :string) - attribute(:title_with_source, :string, source: :t_w_s) - attribute(:title_with_default, :string) - attribute(:email, Test.Support.Types.Email) + attribute(:title, :string, public?: true) + attribute(:second_title, :string, public?: true) + attribute(:title_with_source, :string, source: :t_w_s, public?: true) + attribute(:title_with_default, :string, public?: true) + attribute(:email, Test.Support.Types.Email, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -193,12 +186,12 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:second_title, :string) + attribute(:title, :string, public?: true) + attribute(:second_title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) @@ -210,7 +203,7 @@ defmodule AshPostgres.MigrationGeneratorTest do """ ) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -271,14 +264,14 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -316,14 +309,14 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -357,15 +350,15 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -383,15 +376,15 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:name, :string, allow_nil?: false) + attribute(:name, :string, allow_nil?: false, public?: true) end end - defapi([Post]) + defdomain([Post]) send(self(), {:mix_shell_input, :yes?, true}) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -412,15 +405,15 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:name, :string, allow_nil?: false, default: "fred") + attribute(:name, :string, allow_nil?: false, default: "fred", public?: true) end end - defapi([Post]) + defdomain([Post]) send(self(), {:mix_shell_input, :yes?, true}) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -449,15 +442,15 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -479,13 +472,13 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -507,14 +500,14 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:name, :string, allow_nil?: false) + attribute(:title, :string, public?: true) + attribute(:name, :string, allow_nil?: false, public?: true) end end - defapi([Post]) + defdomain([Post]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -532,15 +525,15 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:name, :string, allow_nil?: false) + attribute(:name, :string, allow_nil?: false, public?: true) end end - defapi([Post]) + defdomain([Post]) send(self(), {:mix_shell_input, :yes?, true}) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -557,15 +550,15 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:name, :string, allow_nil?: false) + attribute(:name, :string, allow_nil?: false, public?: true) end end - defapi([Post]) + defdomain([Post]) send(self(), {:mix_shell_input, :yes?, false}) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -583,17 +576,17 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:name, :string, allow_nil?: false) - attribute(:subject, :string, allow_nil?: false) + attribute(:name, :string, allow_nil?: false, public?: true) + attribute(:subject, :string, allow_nil?: false, public?: true) end end - defapi([Post]) + defdomain([Post]) send(self(), {:mix_shell_input, :yes?, true}) send(self(), {:mix_shell_input, :prompt, "subject"}) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -614,16 +607,16 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:name, :string, allow_nil?: false) - attribute(:subject, :string, allow_nil?: false) + attribute(:name, :string, allow_nil?: false, public?: true) + attribute(:subject, :string, allow_nil?: false, public?: true) end end - defapi([Post]) + defdomain([Post]) send(self(), {:mix_shell_input, :yes?, false}) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -641,21 +634,21 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:foobar, :string) + attribute(:title, :string, public?: true) + attribute(:foobar, :string, public?: true) end end defposts Post2 do attributes do uuid_primary_key(:id) - attribute(:name, :string) + attribute(:name, :string, public?: true) end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -676,7 +669,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end identities do @@ -687,7 +680,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts Post2 do attributes do uuid_primary_key(:id) - attribute(:name, :string) + attribute(:name, :string, public?: true) end identities do @@ -695,9 +688,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -728,8 +721,8 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:example, :string, allow_nil?: false) + attribute(:title, :string, public?: true) + attribute(:example, :string, allow_nil?: false, public?: true) end end @@ -739,9 +732,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -767,16 +760,22 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do - attribute(:id, :integer, generated?: true, allow_nil?: false, primary_key?: true) - attribute(:views, :integer) + attribute(:id, :integer, + generated?: true, + allow_nil?: false, + primary_key?: true, + public?: true + ) + + attribute(:views, :integer, public?: true) end end - defapi([Post]) + defdomain([Post]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -802,18 +801,18 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end - defapi([Post]) + defdomain([Post]) - [api: Api] + [domain: Domain] end - test "returns code(1) if snapshots and resources don't fit", %{api: api} do + test "returns code(1) if snapshots and resources don't fit", %{domain: domain} do assert catch_exit( - AshPostgres.MigrationGenerator.generate(api, + AshPostgres.MigrationGenerator.generate(domain, snapshot_path: "test_snapshot_path", migration_path: "test_migration_path", check: true @@ -837,25 +836,25 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:foobar, :string) + attribute(:title, :string, public?: true) + attribute(:foobar, :string, public?: true) end end defposts Post2 do attributes do uuid_primary_key(:id) - attribute(:name, :string) + attribute(:name, :string, public?: true) end relationships do - belongs_to(:post, Post) + belongs_to(:post, Post, public?: true) end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -871,26 +870,26 @@ defmodule AshPostgres.MigrationGeneratorTest do test "references are inferred automatically if the attribute has a different type" do defposts do attributes do - attribute(:id, :string, primary_key?: true, allow_nil?: false) - attribute(:title, :string) - attribute(:foobar, :string) + attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) + attribute(:title, :string, public?: true) + attribute(:foobar, :string, public?: true) end end defposts Post2 do attributes do - attribute(:id, :string, primary_key?: true, allow_nil?: false) - attribute(:name, :string) + attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) + attribute(:name, :string, public?: true) end relationships do - belongs_to(:post, Post, attribute_type: :string) + belongs_to(:post, Post, attribute_type: :string, public?: true) end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -907,20 +906,22 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:key_id, :uuid, allow_nil?: false) - attribute(:foobar, :string) + attribute(:key_id, :uuid, allow_nil?: false, public?: true) + attribute(:foobar, :string, public?: true) end end defposts Post2 do attributes do uuid_primary_key(:id) - attribute(:name, :string) - attribute(:related_key_id, :uuid) + attribute(:name, :string, public?: true) + attribute(:related_key_id, :uuid, public?: true) end relationships do - belongs_to(:post, Post) + belongs_to(:post, Post) do + public?(true) + end end postgres do @@ -930,9 +931,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -948,8 +949,8 @@ defmodule AshPostgres.MigrationGeneratorTest do test "references merge :match_with and multitenancy attribute" do defresource Org, "orgs" do attributes do - uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + uuid_primary_key(:id, writable?: true, public?: true) + attribute(:name, :string, public?: true) end multitenancy do @@ -961,10 +962,10 @@ defmodule AshPostgres.MigrationGeneratorTest do defresource User, "users" do attributes do uuid_primary_key(:id, writable?: true) - attribute(:secondary_id, :uuid) - attribute(:name, :string) - attribute(:org_id, :uuid) - attribute(:key_id, :uuid) + attribute(:secondary_id, :uuid, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) + attribute(:key_id, :uuid, public?: true) end multitenancy do @@ -973,16 +974,18 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:org, Org) + belongs_to(:org, Org) do + public?(true) + end end end defresource UserThing, "user_things" do attributes do - attribute(:id, :string, primary_key?: true, allow_nil?: false) - attribute(:name, :string) - attribute(:org_id, :uuid) - attribute(:related_key_id, :uuid) + attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) + attribute(:related_key_id, :uuid, public?: true) end multitenancy do @@ -991,8 +994,11 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:org, Org) - belongs_to(:user, User, destination_attribute: :secondary_id) + belongs_to(:org, Org) do + public?(true) + end + + belongs_to(:user, User, destination_attribute: :secondary_id, public?: true) end postgres do @@ -1002,9 +1008,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Org, User, UserThing]) + defdomain([Org, User, UserThing]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1021,7 +1027,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defresource Org, "orgs" do attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end multitenancy do @@ -1033,10 +1039,10 @@ defmodule AshPostgres.MigrationGeneratorTest do defresource User, "users" do attributes do uuid_primary_key(:id, writable?: true) - attribute(:secondary_id, :uuid) - attribute(:name, :string) - attribute(:org_id, :uuid) - attribute(:key_id, :uuid) + attribute(:secondary_id, :uuid, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) + attribute(:key_id, :uuid, public?: true) end multitenancy do @@ -1049,13 +1055,15 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:org, Org) + belongs_to(:org, Org) do + public?(true) + end end end - defapi([Org, User]) + defdomain([Org, User]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1072,25 +1080,27 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:foobar, :string) + attribute(:title, :string, public?: true) + attribute(:foobar, :string, public?: true) end end defposts Post2 do attributes do uuid_primary_key(:id) - attribute(:name, :string) + attribute(:name, :string, public?: true) end relationships do - belongs_to(:post, Post) + belongs_to(:post, Post) do + public?(true) + end end end - defapi([Post, Post2]) + defdomain([Post, Post2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1106,15 +1116,17 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:name, :string) + attribute(:name, :string, public?: true) end relationships do - belongs_to(:post, Post) + belongs_to(:post, Post) do + public?(true) + end end end - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1145,7 +1157,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defresource Org, "orgs" do attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end multitenancy do @@ -1157,9 +1169,9 @@ defmodule AshPostgres.MigrationGeneratorTest do defresource User, "users" do attributes do uuid_primary_key(:id, writable?: true) - attribute(:secondary_id, :uuid) - attribute(:name, :string) - attribute(:org_id, :uuid) + attribute(:secondary_id, :uuid, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) end multitenancy do @@ -1168,15 +1180,17 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:org, Org) + belongs_to(:org, Org) do + public?(true) + end end end defresource UserThing1, "user_things1" do attributes do - attribute(:id, :string, primary_key?: true, allow_nil?: false) - attribute(:name, :string) - attribute(:org_id, :uuid) + attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) end multitenancy do @@ -1185,15 +1199,18 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:org, Org) - belongs_to(:user, User, destination_attribute: :secondary_id) + belongs_to(:org, Org) do + public?(true) + end + + belongs_to(:user, User, destination_attribute: :secondary_id, public?: true) end end defresource UserThing2, "user_things2" do attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end multitenancy do @@ -1202,14 +1219,19 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:org, Org) - belongs_to(:user, User) + belongs_to(:org, Org) do + public?(true) + end + + belongs_to(:user, User) do + public?(true) + end end end - defapi([Org, User, UserThing1, UserThing2]) + defdomain([Org, User, UserThing1, UserThing2]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1238,7 +1260,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:price, :integer) + attribute(:price, :integer, public?: true) end postgres do @@ -1248,9 +1270,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Post]) + defdomain([Post]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1270,7 +1292,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:price, :integer) + attribute(:price, :integer, public?: true) end postgres do @@ -1280,9 +1302,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Post]) + defdomain([Post]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1309,7 +1331,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:price, :integer) + attribute(:price, :integer, public?: true) end postgres do @@ -1319,9 +1341,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defapi([Post]) + defdomain([Post]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1331,11 +1353,11 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:price, :integer) + attribute(:price, :integer, public?: true) end end - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1362,6 +1384,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defmodule Comment do use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer postgres do @@ -1371,7 +1394,7 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) - attribute(:resource_id, :uuid) + attribute(:resource_id, :uuid, public?: true) end actions do @@ -1381,6 +1404,7 @@ defmodule AshPostgres.MigrationGeneratorTest do defmodule Post do use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer postgres do @@ -1398,27 +1422,29 @@ defmodule AshPostgres.MigrationGeneratorTest do relationships do has_many(:comments, Comment, + public?: true, destination_attribute: :resource_id, relationship_context: %{data_layer: %{table: "post_comments"}} ) belongs_to(:best_comment, Comment, + public?: true, destination_attribute: :id, relationship_context: %{data_layer: %{table: "post_comments"}} ) end end - defapi([Post, Comment]) + defdomain([Post, Comment]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, format: false ) - [api: Api] + [domain: Domain] end test "it uses the relationship's table context if it is set" do @@ -1441,22 +1467,32 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:start_date, :date, default: ~D[2022-04-19]) - attribute(:start_time, :time, default: ~T[08:30:45]) - attribute(:timestamp, :utc_datetime, default: ~U[2022-02-02 08:30:30Z]) - attribute(:timestamp_naive, :naive_datetime, default: ~N[2022-02-02 08:30:30]) - attribute(:number, :integer, default: 5) - attribute(:fraction, :float, default: 0.25) - attribute(:decimal, :decimal, default: Decimal.new("123.4567890987654321987")) - attribute(:name, :string, default: "Fred") - attribute(:tag, :atom, default: :value) - attribute(:enabled, :boolean, default: false) + attribute(:start_date, :date, default: ~D[2022-04-19], public?: true) + attribute(:start_time, :time, default: ~T[08:30:45], public?: true) + attribute(:timestamp, :utc_datetime, default: ~U[2022-02-02 08:30:30Z], public?: true) + + attribute(:timestamp_naive, :naive_datetime, + default: ~N[2022-02-02 08:30:30], + public?: true + ) + + attribute(:number, :integer, default: 5, public?: true) + attribute(:fraction, :float, default: 0.25, public?: true) + + attribute(:decimal, :decimal, + default: Decimal.new("123.4567890987654321987"), + public?: true + ) + + attribute(:name, :string, default: "Fred", public?: true) + attribute(:tag, :atom, default: :value, public?: true) + attribute(:enabled, :boolean, default: false, public?: true) end end - defapi([Post]) + defdomain([Post]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1502,15 +1538,15 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:product_code, :term, default: {"xyz"}) + attribute(:product_code, :term, default: {"xyz"}, public?: true) end end - defapi([Post]) + defdomain([Post]) log = capture_log(fn -> - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1539,12 +1575,13 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do attributes do uuid_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end defmodule Comment do use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer postgres do @@ -1557,15 +1594,17 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:post, Post) + belongs_to(:post, Post) do + public?(true) + end end end - defapi([Post, Comment]) + defdomain([Post, Comment]) Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, @@ -1578,14 +1617,20 @@ defmodule AshPostgres.MigrationGeneratorTest do test "when changing the primary key, it changes properly" do defposts do attributes do - attribute(:id, :uuid, primary_key?: false, default: &Ecto.UUID.generate/0) + attribute(:id, :uuid, + primary_key?: false, + default: &Ecto.UUID.generate/0, + public?: true + ) + uuid_primary_key(:guid) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end defmodule Comment do use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer postgres do @@ -1598,13 +1643,15 @@ defmodule AshPostgres.MigrationGeneratorTest do end relationships do - belongs_to(:post, Post) + belongs_to(:post, Post) do + public?(true) + end end end - defapi([Post, Comment]) + defdomain([Post, Comment]) - AshPostgres.MigrationGenerator.generate(Api, + AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 9fd56055..70cdbf7b 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -1,18 +1,18 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.MultitenancyTest.{Api, Org, Post, User} + alias AshPostgres.MultitenancyTest.{Org, Post, User} setup do org1 = Org - |> Ash.Changeset.new(name: "test1") - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{name: "test1"}) + |> Ash.create!() org2 = Org - |> Ash.Changeset.new(name: "test2") - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{name: "test2"}) + |> Ash.create!() [org1: org1, org2: org2] end @@ -34,17 +34,17 @@ defmodule AshPostgres.Test.MultitenancyTest do assert [%{id: ^org_id}] = Org |> Ash.Query.set_tenant(tenant(org1)) - |> Api.read!() + |> Ash.read!() end test "context multitenancy works with policies", %{org1: org1} do Post - |> Ash.Changeset.new(name: "foo") + |> Ash.Changeset.for_create(:create, %{name: "foo"}) |> Ash.Changeset.set_tenant(tenant(org1)) - |> Api.create!() + |> Ash.create!() |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true) |> Ash.Changeset.set_tenant(tenant(org1)) - |> Api.update!() + |> Ash.update!() end test "attribute multitenancy is set on creation" do @@ -52,29 +52,29 @@ defmodule AshPostgres.Test.MultitenancyTest do org = Org - |> Ash.Changeset.new(name: "test3") + |> Ash.Changeset.for_create(:create, %{name: "test3"}) |> Ash.Changeset.set_tenant("org_#{uuid}") - |> Api.create!() + |> Ash.create!() assert org.id == uuid end test "schema multitenancy works", %{org1: org1, org2: org2} do Post - |> Ash.Changeset.new(name: "foo") + |> Ash.Changeset.for_create(:create, %{name: "foo"}) |> Ash.Changeset.set_tenant(tenant(org1)) - |> Api.create!() + |> Ash.create!() - assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Api.read!() - assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Api.read!() + assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Ash.read!() + assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Ash.read!() end test "schema rename on update works", %{org1: org1} do new_uuid = Ash.UUID.generate() org1 - |> Ash.Changeset.new(id: new_uuid) - |> Api.update!() + |> Ash.Changeset.for_update(:update, %{id: new_uuid}) + |> Ash.update!() new_tenant = "org_#{new_uuid}" @@ -91,106 +91,106 @@ defmodule AshPostgres.Test.MultitenancyTest do org = Org |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() user = User |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() - assert Api.load!(user, :org).org.id == org.id + assert Ash.load!(user, :org).org.id == org.id end test "loading context multitenant resources from attribute multitenant resources works" do org = Org |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() user1 = User - |> Ash.Changeset.new(%{name: "a"}) + |> Ash.Changeset.for_create(:create, %{name: "a"}) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() user2 = User - |> Ash.Changeset.new(%{name: "b"}) + |> Ash.Changeset.for_create(:create, %{name: "b"}) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() user1_id = user1.id user2_id = user2.id assert [%{id: ^user1_id}, %{id: ^user2_id}] = - Api.load!(org, users: Ash.Query.sort(User, :name)).users + Ash.load!(org, users: Ash.Query.sort(User, :name)).users end test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do - org = Org |> Ash.Changeset.new() |> Api.create!() - user = User |> Ash.Changeset.new() |> Api.create!() + org = Org |> Ash.Changeset.new() |> Ash.create!() + user = User |> Ash.Changeset.new() |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{}, tenant: tenant(org)) |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() end test "loading attribute multitenant resources with limits from context multitenant resources works" do org = Org |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() user = User |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() - assert Api.load!(user, :org).org.id == org.id + assert Ash.load!(user, :org).org.id == org.id end test "loading context multitenant resources with limits from attribute multitenant resources works" do org = Org |> Ash.Changeset.new() - |> Api.create!() + |> Ash.create!() user1 = User - |> Ash.Changeset.new(%{name: "a"}) + |> Ash.Changeset.for_create(:create, %{name: "a"}) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() user2 = User - |> Ash.Changeset.new(%{name: "b"}) + |> Ash.Changeset.for_create(:create, %{name: "b"}) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() user1_id = user1.id user2_id = user2.id assert [%{id: ^user1_id}, %{id: ^user2_id}] = - Api.load!(org, users: Ash.Query.sort(Ash.Query.limit(User, 10), :name)).users + Ash.load!(org, users: Ash.Query.sort(Ash.Query.limit(User, 10), :name)).users end test "unique constraints are properly scoped", %{org1: org1} do post = Post - |> Ash.Changeset.new(%{}) + |> Ash.Changeset.for_create(:create, %{}) |> Ash.Changeset.set_tenant(tenant(org1)) - |> Api.create!() + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/Invalid value provided for id: has already been taken/, fn -> Post - |> Ash.Changeset.new(%{id: post.id}) + |> Ash.Changeset.for_create(:create, %{id: post.id}) |> Ash.Changeset.set_tenant(tenant(org1)) - |> Api.create!() + |> Ash.create!() end end end diff --git a/test/polymorphism_test.exs b/test/polymorphism_test.exs index e176719b..4612a3c8 100644 --- a/test/polymorphism_test.exs +++ b/test/polymorphism_test.exs @@ -1,29 +1,29 @@ defmodule AshPostgres.PolymorphismTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post, Rating} + alias AshPostgres.Test.{Post, Rating} require Ash.Query test "you can create related data" do Post |> Ash.Changeset.for_create(:create, rating: %{score: 10}) - |> Api.create!() + |> Ash.create!() assert [%{score: 10}] = Rating |> Ash.Query.set_context(%{data_layer: %{table: "post_ratings"}}) - |> Api.read!() + |> Ash.read!() end test "you can read related data" do Post |> Ash.Changeset.for_create(:create, rating: %{score: 10}) - |> Api.create!() + |> Ash.create!() assert [%{score: 10}] = Post |> Ash.Query.load(:ratings) - |> Api.read_one!() + |> Ash.read_one!() |> Map.get(:ratings) end end diff --git a/test/primary_key_test.exs b/test/primary_key_test.exs index 7f5ba714..af12e799 100644 --- a/test/primary_key_test.exs +++ b/test/primary_key_test.exs @@ -1,16 +1,17 @@ defmodule AshPostgres.Test.PrimaryKeyTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, IntegerPost, Post, PostView} + alias AshPostgres.Test.{IntegerPost, Post, PostView} require Ash.Query test "creates record with integer primary key" do - assert %IntegerPost{} = IntegerPost |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() + assert %IntegerPost{} = + IntegerPost |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.create!() end test "creates record with uuid primary key" do - assert %Post{} = Post |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() + assert %Post{} = Post |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.create!() end describe "resources without a primary key" do @@ -18,12 +19,12 @@ defmodule AshPostgres.Test.PrimaryKeyTest do post = Post |> Ash.Changeset.for_action(:create, %{title: "not very interesting"}) - |> Api.create!() + |> Ash.create!() assert {:ok, view} = PostView |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id}) - |> Api.create() + |> Ash.create() assert view.browser == :firefox assert view.post_id == post.id @@ -34,14 +35,14 @@ defmodule AshPostgres.Test.PrimaryKeyTest do post = Post |> Ash.Changeset.for_action(:create, %{title: "not very interesting"}) - |> Api.create!() + |> Ash.create!() expected = PostView |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id}) - |> Api.create!() + |> Ash.create!() - assert {:ok, [actual]} = Api.read(PostView) + assert {:ok, [actual]} = Ash.read(PostView) assert actual.time == expected.time assert actual.browser == expected.browser diff --git a/test/references_test.exs b/test/references_test.exs index b192597b..afb89acd 100644 --- a/test/references_test.exs +++ b/test/references_test.exs @@ -8,11 +8,12 @@ defmodule AshPostgres.ReferencesTest do defmodule Org do @moduledoc false use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end multitenancy do @@ -33,14 +34,15 @@ defmodule AshPostgres.ReferencesTest do defmodule User do @moduledoc false use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer attributes do uuid_primary_key(:id, writable?: true) - attribute(:secondary_id, :uuid) - attribute(:foo_id, :uuid) - attribute(:name, :string) - attribute(:org_id, :uuid) + attribute(:secondary_id, :uuid, public?: true) + attribute(:foo_id, :uuid, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) end multitenancy do @@ -49,7 +51,9 @@ defmodule AshPostgres.ReferencesTest do end relationships do - belongs_to(:org, Org) + belongs_to(:org, Org) do + public?(true) + end end postgres do @@ -66,13 +70,14 @@ defmodule AshPostgres.ReferencesTest do defmodule UserThing do @moduledoc false use Ash.Resource, + domain: nil, data_layer: AshPostgres.DataLayer attributes do - attribute(:id, :string, primary_key?: true, allow_nil?: false) - attribute(:name, :string) - attribute(:org_id, :uuid) - attribute(:foo_id, :uuid) + attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) + attribute(:foo_id, :uuid, public?: true) end multitenancy do diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs index 4ac3aa7e..ef8a3846 100644 --- a/test/rel_with_parent_filter_test.exs +++ b/test/rel_with_parent_filter_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.RelWithParentFilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Author} + alias AshPostgres.Test.Author require Ash.Query @@ -9,11 +9,11 @@ defmodule AshPostgres.RelWithParentFilterTest do %{id: author_id} = Author |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) - |> Api.create!() + |> Ash.create!() Author |> Ash.Changeset.for_create(:create, %{first_name: "John"}) - |> Api.create!() + |> Ash.create!() # here we get the expected result of 1 because it is done in the same query assert %{num_of_authors_with_same_first_name: 1} = @@ -21,18 +21,18 @@ defmodule AshPostgres.RelWithParentFilterTest do |> Ash.Query.for_read(:read) |> Ash.Query.filter(id == ^author_id) |> Ash.Query.load(:num_of_authors_with_same_first_name) - |> Api.read_one!() + |> Ash.read_one!() end test "filter on relationship using parent works as expected when loading relationship" do %{id: author_id} = Author |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) - |> Api.create!() + |> Ash.create!() Author |> Ash.Changeset.for_create(:create, %{first_name: "John"}) - |> Api.create!() + |> Ash.create!() assert %{authors_with_same_first_name: authors} = Author @@ -43,7 +43,7 @@ defmodule AshPostgres.RelWithParentFilterTest do # but when doing that it does a inner lateral join # instead of using the id from the parent relationship |> Ash.Query.load(:authors_with_same_first_name) - |> Api.read_one!() + |> Ash.read_one!() assert length(authors) == 1 end diff --git a/test/schema_test.exs b/test/schema_test.exs index 1dfa052b..c8be1cf8 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -1,12 +1,12 @@ defmodule AshPostgres.SchemaTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Author, Profile} + alias AshPostgres.Test.{Author, Profile} require Ash.Query setup do - [author: Api.create!(Ash.Changeset.for_create(Author, :create, %{}))] + [author: Ash.create!(Ash.Changeset.for_create(Author, :create, %{}))] end test "data can be created", %{author: author} do @@ -14,16 +14,16 @@ defmodule AshPostgres.SchemaTest do Profile |> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() end test "data can be read", %{author: author} do Profile |> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() - assert [%{description: "foo"}] = Profile |> Api.read!() + assert [%{description: "foo"}] = Profile |> Ash.read!() end test "they can be filtered across", %{author: author} do @@ -31,33 +31,33 @@ defmodule AshPostgres.SchemaTest do Profile |> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() - Api.create!(Ash.Changeset.for_create(Author, :create, %{})) + Ash.create!(Ash.Changeset.for_create(Author, :create, %{})) assert [_] = Author |> Ash.Query.filter(profile.id == ^profile.id) - |> Api.read!() + |> Ash.read!() assert [_] = Profile |> Ash.Query.filter(author.id == ^author.id) - |> Api.read!() + |> Ash.read!() end test "aggregates work across schemas", %{author: author} do Profile |> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() assert [%{profile_description: "foo"}] = Author |> Ash.Query.filter(profile_description == "foo") |> Ash.Query.load(:profile_description) - |> Api.read!() + |> Ash.read!() - assert %{profile_description: "foo"} = Api.load!(author, :profile_description) + assert %{profile_description: "foo"} = Ash.load!(author, :profile_description) end end diff --git a/test/select_test.exs b/test/select_test.exs index 5287b5fe..1d30aaea 100644 --- a/test/select_test.exs +++ b/test/select_test.exs @@ -1,15 +1,15 @@ defmodule AshPostgres.SelectTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "values not selected in the query are not present in the response" do Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() - assert [%{title: nil}] = Api.read!(Ash.Query.select(Post, :id)) + assert [%{title: %Ash.NotLoaded{}}] = Ash.read!(Ash.Query.select(Post, :id)) end end diff --git a/test/sort_test.exs b/test/sort_test.exs index 25f6a8b9..1d190e8b 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -1,30 +1,30 @@ defmodule AshPostgres.SortTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Comment, Post, PostLink} + alias AshPostgres.Test.{Comment, Post, PostLink} require Ash.Query require Ash.Sort test "multi-column sorts work" do Post - |> Ash.Changeset.new(%{title: "aaa", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "aaa", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "bbb", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0}) + |> Ash.create!() assert [ %{title: "aaa", score: 0}, %{title: "aaa", score: 1}, %{title: "bbb"} ] = - Api.read!( + Ash.read!( Post |> Ash.Query.load(:count_of_comments) |> Ash.Query.sort(title: :asc, score: :asc) @@ -34,31 +34,31 @@ defmodule AshPostgres.SortTest do test "multi-column sorts work on inclusion" do post = Post - |> Ash.Changeset.new(%{title: "aaa", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "aaa", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "bbb", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0}) + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "aaa", likes: 1}) + |> Ash.Changeset.for_create(:create, %{title: "aaa", likes: 1}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "bbb", likes: 1}) + |> Ash.Changeset.for_create(:create, %{title: "bbb", likes: 1}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() Comment - |> Ash.Changeset.new(%{title: "aaa", likes: 2}) + |> Ash.Changeset.for_create(:create, %{title: "aaa", likes: 2}) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Api.create!() + |> Ash.create!() posts = Post @@ -71,7 +71,7 @@ defmodule AshPostgres.SortTest do |> Ash.Query.limit(1) ]) |> Ash.Query.sort([:title, :score]) - |> Api.read!() + |> Ash.read!() assert [ %{title: "aaa", comments: [%{title: "aaa"}]}, @@ -82,23 +82,23 @@ defmodule AshPostgres.SortTest do test "multicolumn sort works with a select statement" do Post - |> Ash.Changeset.new(%{title: "aaa", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "aaa", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "bbb", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0}) + |> Ash.create!() assert [ %{title: "aaa", score: 0}, %{title: "aaa", score: 1}, %{title: "bbb"} ] = - Api.read!( + Ash.read!( Post |> Ash.Query.sort(title: :asc, score: :asc) |> Ash.Query.select([:title, :score]) @@ -108,43 +108,43 @@ defmodule AshPostgres.SortTest do test "sorting when joining to a many to many relationship sorts properly" do post1 = Post - |> Ash.Changeset.new(%{title: "aaa", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "bbb", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 1}) + |> Ash.create!() post3 = Post - |> Ash.Changeset.new(%{title: "ccc", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "ccc", score: 0}) + |> Ash.create!() PostLink |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:source_post, post1, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append) - |> Api.create!() + |> Ash.create!() PostLink |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:source_post, post2, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append) - |> Api.create!() + |> Ash.create!() PostLink |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:source_post, post3, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append) - |> Api.create!() + |> Ash.create!() assert [ %{title: "aaa"}, %{title: "bbb"}, %{title: "ccc"} ] = - Api.read!( + Ash.read!( Post |> Ash.Query.sort(title: :asc) |> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"]) @@ -155,7 +155,7 @@ defmodule AshPostgres.SortTest do %{title: "bbb"}, %{title: "aaa"} ] = - Api.read!( + Ash.read!( Post |> Ash.Query.sort(title: :desc) |> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"] or title == "aaa") @@ -166,7 +166,7 @@ defmodule AshPostgres.SortTest do %{title: "bbb"}, %{title: "aaa"} ] = - Api.read!( + Ash.read!( Post |> Ash.Query.sort(title: :desc) |> Ash.Query.filter( @@ -180,48 +180,48 @@ defmodule AshPostgres.SortTest do Post |> Ash.Query.load(:count_of_comments) |> Ash.Query.sort(:c_times_p) - |> Api.read!() + |> Ash.read!() end test "calculations can sort on expressions" do post1 = Post - |> Ash.Changeset.new(%{title: "aaa", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() post2 = Post - |> Ash.Changeset.new(%{title: "bbb", score: 1}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 1}) + |> Ash.create!() post3 = Post - |> Ash.Changeset.new(%{title: "ccc", score: 0}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "ccc", score: 0}) + |> Ash.create!() PostLink |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:source_post, post1, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append) - |> Api.create!() + |> Ash.create!() PostLink |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:source_post, post2, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append) - |> Api.create!() + |> Ash.create!() PostLink |> Ash.Changeset.new() |> Ash.Changeset.manage_relationship(:source_post, post3, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append) - |> Api.create!() + |> Ash.create!() posts_query = Ash.Query.sort(Post, Ash.Sort.expr_sort(source(post_links.state))) Post |> Ash.Query.load(linked_posts: posts_query) - |> Api.read!() + |> Ash.read!() end end diff --git a/test/support/api.ex b/test/support/api.ex deleted file mode 100644 index 270256ff..00000000 --- a/test/support/api.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule AshPostgres.Test.Api do - @moduledoc false - use Ash.Api - - resources do - registry(AshPostgres.Test.Registry) - end -end diff --git a/test/support/complex_calculations/api.ex b/test/support/complex_calculations/api.ex deleted file mode 100644 index d7202c47..00000000 --- a/test/support/complex_calculations/api.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule AshPostgres.Test.ComplexCalculations.Api do - @moduledoc false - use Ash.Api - - resources do - registry(AshPostgres.Test.ComplexCalculations.Registry) - end -end diff --git a/test/support/complex_calculations/domain.ex b/test/support/complex_calculations/domain.ex new file mode 100644 index 00000000..76d066c2 --- /dev/null +++ b/test/support/complex_calculations/domain.ex @@ -0,0 +1,17 @@ +defmodule AshPostgres.Test.ComplexCalculations.Domain do + @moduledoc false + use Ash.Domain + + resources do + resource(AshPostgres.Test.ComplexCalculations.Certification) + resource(AshPostgres.Test.ComplexCalculations.Skill) + resource(AshPostgres.Test.ComplexCalculations.Documentation) + resource(AshPostgres.Test.ComplexCalculations.Channel) + resource(AshPostgres.Test.ComplexCalculations.DMChannel) + resource(AshPostgres.Test.ComplexCalculations.ChannelMember) + end + + authorization do + authorize(:when_requested) + end +end diff --git a/test/support/complex_calculations/registry.ex b/test/support/complex_calculations/registry.ex deleted file mode 100644 index 29f3afcb..00000000 --- a/test/support/complex_calculations/registry.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule AshPostgres.Test.ComplexCalculations.Registry do - @moduledoc false - use Ash.Registry - - entries do - entry(AshPostgres.Test.ComplexCalculations.Certification) - entry(AshPostgres.Test.ComplexCalculations.Skill) - entry(AshPostgres.Test.ComplexCalculations.Documentation) - entry(AshPostgres.Test.ComplexCalculations.Channel) - entry(AshPostgres.Test.ComplexCalculations.DMChannel) - entry(AshPostgres.Test.ComplexCalculations.ChannelMember) - end -end diff --git a/test/support/complex_calculations/resources/certification.ex b/test/support/complex_calculations/resources/certification.ex index 72de4f85..d41ca980 100644 --- a/test/support/complex_calculations/resources/certification.ex +++ b/test/support/complex_calculations/resources/certification.ex @@ -1,8 +1,11 @@ defmodule AshPostgres.Test.ComplexCalculations.Certification do @moduledoc false - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, + data_layer: AshPostgres.DataLayer actions do + default_accept(:*) defaults([:create, :read, :update, :destroy]) end @@ -43,6 +46,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Certification do end relationships do - has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill) + has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill) do + public?(true) + end end end diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex index 6d9d3a85..eed91879 100644 --- a/test/support/complex_calculations/resources/channel.ex +++ b/test/support/complex_calculations/resources/channel.ex @@ -1,20 +1,22 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] require Ash.Expr actions do + default_accept(:*) defaults([:create, :read, :update, :destroy]) end attributes do uuid_primary_key(:id) - create_timestamp(:created_at, private?: false) - update_timestamp(:updated_at, private?: false) + create_timestamp(:created_at, public?: true) + update_timestamp(:updated_at, public?: true) end postgres do @@ -23,30 +25,36 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do end relationships do - has_many(:channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember) + has_many(:channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember) do + public?(true) + end has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + public?(true) destination_attribute(:channel_id) from_many?(true) sort(created_at: :asc) end has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + public?(true) destination_attribute(:channel_id) from_many?(true) sort(created_at: :desc) end has_one :dm_channel, AshPostgres.Test.ComplexCalculations.DMChannel do - api(AshPostgres.Test.ComplexCalculations.Api) + public?(true) + domain(AshPostgres.Test.ComplexCalculations.Domain) destination_attribute(:id) end has_one :dm_channel_with_same_id, AshPostgres.Test.ComplexCalculations.DMChannel do + public?(true) no_attributes?(true) from_many?(true) filter(expr(parent(id) == id)) - api(AshPostgres.Test.ComplexCalculations.Api) + domain(AshPostgres.Test.ComplexCalculations.Domain) end end diff --git a/test/support/complex_calculations/resources/channel_member.ex b/test/support/complex_calculations/resources/channel_member.ex index 0d78a294..69e631e4 100644 --- a/test/support/complex_calculations/resources/channel_member.ex +++ b/test/support/complex_calculations/resources/channel_member.ex @@ -1,18 +1,21 @@ defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end attributes do uuid_primary_key(:id) - create_timestamp(:created_at, private?: false) - update_timestamp(:updated_at, private?: false) + create_timestamp(:created_at, public?: true) + update_timestamp(:updated_at, public?: true) end postgres do @@ -21,8 +24,8 @@ defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do end relationships do - belongs_to(:user, AshPostgres.Test.User, api: AshPostgres.Test.Api, attribute_writable?: true) + belongs_to(:user, AshPostgres.Test.User, domain: AshPostgres.Test.Domain, public?: true) - belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel, attribute_writable?: true) + belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel, public?: true) end end diff --git a/test/support/complex_calculations/resources/dm_channel.ex b/test/support/complex_calculations/resources/dm_channel.ex index b8b74ad1..ed3a6397 100644 --- a/test/support/complex_calculations/resources/dm_channel.ex +++ b/test/support/complex_calculations/resources/dm_channel.ex @@ -1,20 +1,23 @@ defmodule AshPostgres.Test.ComplexCalculations.DMChannel do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] require Ash.Expr actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end attributes do uuid_primary_key(:id) - create_timestamp(:created_at, private?: false) - update_timestamp(:updated_at, private?: false) + create_timestamp(:created_at, public?: true) + update_timestamp(:updated_at, public?: true) end postgres do @@ -24,16 +27,19 @@ defmodule AshPostgres.Test.ComplexCalculations.DMChannel do relationships do has_many :channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember do + public?(true) destination_attribute(:channel_id) end has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + public?(true) destination_attribute(:channel_id) from_many?(true) sort(created_at: :asc) end has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do + public?(true) destination_attribute(:channel_id) from_many?(true) sort(created_at: :desc) diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex index 757f09bd..7ec79404 100644 --- a/test/support/complex_calculations/resources/documentation.ex +++ b/test/support/complex_calculations/resources/documentation.ex @@ -1,8 +1,12 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do @moduledoc false - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, + data_layer: AshPostgres.DataLayer actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -15,12 +19,13 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do constraints: [ one_of: [:demonstrated, :performed, :approved, :reopened] ], + public?: true, allow_nil?: false ) - attribute(:documented_at, :utc_datetime_usec) - create_timestamp(:inserted_at, private?: false) - update_timestamp(:updated_at, private?: false) + attribute(:documented_at, :utc_datetime_usec, public?: true) + create_timestamp(:inserted_at, public?: true, writable?: true) + update_timestamp(:updated_at, public?: true, writable?: true) end calculations do @@ -43,6 +48,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do end relationships do - belongs_to(:skill, AshPostgres.Test.ComplexCalculations.Skill) + belongs_to(:skill, AshPostgres.Test.ComplexCalculations.Skill) do + public?(true) + end end end diff --git a/test/support/complex_calculations/resources/skill.ex b/test/support/complex_calculations/resources/skill.ex index 5a38037e..415a87b6 100644 --- a/test/support/complex_calculations/resources/skill.ex +++ b/test/support/complex_calculations/resources/skill.ex @@ -1,8 +1,12 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do @moduledoc false - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, + data_layer: AshPostgres.DataLayer actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -14,7 +18,7 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do attributes do uuid_primary_key(:id) - attribute(:removed, :boolean, default: false, allow_nil?: false) + attribute(:removed, :boolean, default: false, allow_nil?: false, public?: true) end calculations do @@ -37,13 +41,17 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do end relationships do - belongs_to(:certification, AshPostgres.Test.ComplexCalculations.Certification) + belongs_to(:certification, AshPostgres.Test.ComplexCalculations.Certification) do + public?(true) + end has_many :documentations, AshPostgres.Test.ComplexCalculations.Documentation do + public?(true) sort(timestamp: :desc, inserted_at: :desc) end has_one :latest_documentation, AshPostgres.Test.ComplexCalculations.Documentation do + public?(true) sort(timestamp: :desc, inserted_at: :desc) end end diff --git a/test/support/concat.ex b/test/support/concat.ex index af107add..353bbfa4 100644 --- a/test/support/concat.ex +++ b/test/support/concat.ex @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.Concat do @moduledoc false - use Ash.Calculation + use Ash.Resource.Calculation require Ash.Query def init(opts) do @@ -11,16 +11,16 @@ defmodule AshPostgres.Test.Concat do end end - def expression(opts, %{separator: separator}) do + def expression(opts, %{arguments: %{separator: separator}}) do Enum.reduce(opts[:keys], nil, fn key, expr -> if expr do if separator do - Ash.Query.expr(^expr <> ^separator <> ref(^key)) + expr(^expr <> ^separator <> ^ref(key)) else - Ash.Query.expr(^expr <> ref(^key)) + expr(^expr <> ^ref(key)) end else - Ash.Query.expr(ref(^key)) + expr(^ref(key)) end end) end diff --git a/test/support/domain.ex b/test/support/domain.ex new file mode 100644 index 00000000..9b7e9057 --- /dev/null +++ b/test/support/domain.ex @@ -0,0 +1,27 @@ +defmodule AshPostgres.Test.Domain do + @moduledoc false + use Ash.Domain + + resources do + resource(AshPostgres.Test.Post) + resource(AshPostgres.Test.Comment) + resource(AshPostgres.Test.IntegerPost) + resource(AshPostgres.Test.Rating) + resource(AshPostgres.Test.PostLink) + resource(AshPostgres.Test.PostView) + resource(AshPostgres.Test.Author) + resource(AshPostgres.Test.Profile) + resource(AshPostgres.Test.User) + resource(AshPostgres.Test.Account) + resource(AshPostgres.Test.Organization) + resource(AshPostgres.Test.Manager) + resource(AshPostgres.Test.Entity) + resource(AshPostgres.Test.TempEntity) + resource(AshPostgres.Test.Record) + resource(AshPostgres.Test.PostFollower) + end + + authorization do + authorize(:when_requested) + end +end diff --git a/test/support/multitenancy/api.ex b/test/support/multitenancy/api.ex deleted file mode 100644 index b0bebca2..00000000 --- a/test/support/multitenancy/api.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule AshPostgres.MultitenancyTest.Api do - @moduledoc false - use Ash.Api - - resources do - registry(AshPostgres.MultitenancyTest.Registry) - end -end diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex new file mode 100644 index 00000000..6384d0da --- /dev/null +++ b/test/support/multitenancy/domain.ex @@ -0,0 +1,10 @@ +defmodule AshPostgres.MultitenancyTest.Domain do + @moduledoc false + use Ash.Domain + + resources do + resource(AshPostgres.MultitenancyTest.Org) + resource(AshPostgres.MultitenancyTest.User) + resource(AshPostgres.MultitenancyTest.Post) + end +end diff --git a/test/support/multitenancy/registry.ex b/test/support/multitenancy/registry.ex deleted file mode 100644 index 0a8eef51..00000000 --- a/test/support/multitenancy/registry.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule AshPostgres.MultitenancyTest.Registry do - @moduledoc false - use Ash.Registry - - entries do - entry(AshPostgres.MultitenancyTest.Org) - entry(AshPostgres.MultitenancyTest.User) - entry(AshPostgres.MultitenancyTest.Post) - end -end diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index fc04c8c8..1ac913ab 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.MultitenancyTest.Org do @moduledoc false use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, data_layer: AshPostgres.DataLayer identities do @@ -9,10 +10,12 @@ defmodule AshPostgres.MultitenancyTest.Org do attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -33,8 +36,15 @@ defmodule AshPostgres.MultitenancyTest.Org do end relationships do - has_many(:posts, AshPostgres.MultitenancyTest.Post, destination_attribute: :org_id) - has_many(:users, AshPostgres.MultitenancyTest.User, destination_attribute: :org_id) + has_many(:posts, AshPostgres.MultitenancyTest.Post, + destination_attribute: :org_id, + public?: true + ) + + has_many(:users, AshPostgres.MultitenancyTest.User, + destination_attribute: :org_id, + public?: true + ) end def tenant("org_" <> tenant) do diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index 963e54b0..84452d8c 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.MultitenancyTest.Post do @moduledoc false use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] @@ -17,10 +18,12 @@ defmodule AshPostgres.MultitenancyTest.Post do attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) update(:update_with_policy) @@ -38,8 +41,14 @@ defmodule AshPostgres.MultitenancyTest.Post do end relationships do - belongs_to(:org, AshPostgres.MultitenancyTest.Org) - belongs_to(:user, AshPostgres.MultitenancyTest.User) - has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id) + belongs_to(:org, AshPostgres.MultitenancyTest.Org) do + public?(true) + end + + belongs_to(:user, AshPostgres.MultitenancyTest.User) do + public?(true) + end + + has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id, public?: true) end end diff --git a/test/support/multitenancy/resources/user.ex b/test/support/multitenancy/resources/user.ex index 9fd1c41f..05ab9670 100644 --- a/test/support/multitenancy/resources/user.ex +++ b/test/support/multitenancy/resources/user.ex @@ -1,12 +1,13 @@ defmodule AshPostgres.MultitenancyTest.User do @moduledoc false use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, data_layer: AshPostgres.DataLayer attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) - attribute(:org_id, :uuid) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) end postgres do @@ -15,6 +16,8 @@ defmodule AshPostgres.MultitenancyTest.User do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -28,7 +31,9 @@ defmodule AshPostgres.MultitenancyTest.User do end relationships do - belongs_to(:org, AshPostgres.MultitenancyTest.Org) + belongs_to(:org, AshPostgres.MultitenancyTest.Org) do + public?(true) + end end def parse_tenant("org_" <> id), do: id diff --git a/test/support/registry.ex b/test/support/registry.ex deleted file mode 100644 index 9502fd66..00000000 --- a/test/support/registry.ex +++ /dev/null @@ -1,23 +0,0 @@ -defmodule AshPostgres.Test.Registry do - @moduledoc false - use Ash.Registry - - entries do - entry(AshPostgres.Test.Post) - entry(AshPostgres.Test.Comment) - entry(AshPostgres.Test.IntegerPost) - entry(AshPostgres.Test.Rating) - entry(AshPostgres.Test.PostLink) - entry(AshPostgres.Test.PostView) - entry(AshPostgres.Test.Author) - entry(AshPostgres.Test.Profile) - entry(AshPostgres.Test.User) - entry(AshPostgres.Test.Account) - entry(AshPostgres.Test.Organization) - entry(AshPostgres.Test.Manager) - entry(AshPostgres.Test.Entity) - entry(AshPostgres.Test.TempEntity) - entry(AshPostgres.Test.Record) - entry(AshPostgres.Test.PostFollower) - end -end diff --git a/test/support/relationships/comments_containing_title.ex b/test/support/relationships/comments_containing_title.ex index e0ca6a56..a58a6b93 100644 --- a/test/support/relationships/comments_containing_title.ex +++ b/test/support/relationships/comments_containing_title.ex @@ -13,7 +13,7 @@ defmodule AshPostgres.Test.Post.CommentsContainingTitle do query |> Ash.Query.filter(post_id in ^post_ids) |> Ash.Query.filter(contains(title, post.title)) - |> AshPostgres.Test.Api.read!(actor: actor, authorize?: authorize?) + |> Ash.read!(actor: actor, authorize?: authorize?) |> Enum.group_by(& &1.post_id)} end diff --git a/test/support/resources/account.ex b/test/support/resources/account.ex index 7f30bf28..c771b9ce 100644 --- a/test/support/resources/account.ex +++ b/test/support/resources/account.ex @@ -1,8 +1,12 @@ defmodule AshPostgres.Test.Account do @moduledoc false - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -12,7 +16,7 @@ defmodule AshPostgres.Test.Account do attributes do uuid_primary_key(:id) - attribute(:is_active, :boolean) + attribute(:is_active, :boolean, public?: true) end calculations do @@ -30,6 +34,8 @@ defmodule AshPostgres.Test.Account do end relationships do - belongs_to(:user, AshPostgres.Test.User) + belongs_to(:user, AshPostgres.Test.User) do + public?(true) + end end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 51d32184..3c2dd82a 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -1,11 +1,12 @@ defmodule AshPostgres.Test.Author do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer defmodule RuntimeFullName do @moduledoc false - use Ash.Calculation + use Ash.Resource.Calculation def calculate(records, _, _) do Enum.map(records, fn record -> @@ -21,21 +22,29 @@ defmodule AshPostgres.Test.Author do attributes do uuid_primary_key(:id, writable?: true) - attribute(:first_name, :string) - attribute(:last_name, :string) - attribute(:bio, AshPostgres.Test.Bio) - attribute(:badges, {:array, :atom}) + attribute(:first_name, :string, public?: true) + attribute(:last_name, :string, public?: true) + attribute(:bio, AshPostgres.Test.Bio, public?: true) + attribute(:badges, {:array, :atom}, public?: true) end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end relationships do - has_one(:profile, AshPostgres.Test.Profile) - has_many(:posts, AshPostgres.Test.Post) + has_one(:profile, AshPostgres.Test.Profile) do + public?(true) + end + + has_many(:posts, AshPostgres.Test.Post) do + public?(true) + end has_many :authors_with_same_first_name, __MODULE__ do + public?(true) source_attribute(:first_name) destination_attribute(:first_name) filter(expr(parent(id) != id)) diff --git a/test/support/resources/bio.ex b/test/support/resources/bio.ex index 9f89fbef..59dd2dfb 100644 --- a/test/support/resources/bio.ex +++ b/test/support/resources/bio.ex @@ -3,15 +3,18 @@ defmodule AshPostgres.Test.Bio do use Ash.Resource, data_layer: :embedded actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end attributes do - attribute(:title, :string) - attribute(:bio, :string) - attribute(:years_of_experience, :integer) + attribute(:title, :string, public?: true) + attribute(:bio, :string, public?: true) + attribute(:years_of_experience, :integer, public?: true) attribute :list_of_strings, {:array, :string} do + public?(true) allow_nil?(true) default(nil) end diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index a7029acd..c8888a8d 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.Comment do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -23,6 +24,7 @@ defmodule AshPostgres.Test.Comment do end actions do + default_accept(:*) defaults([:read, :update, :destroy]) create :create do @@ -35,10 +37,10 @@ defmodule AshPostgres.Test.Comment do attributes do uuid_primary_key(:id) - attribute(:title, :string) - attribute(:likes, :integer) - attribute(:arbitrary_timestamp, :utc_datetime_usec) - create_timestamp(:created_at, writable?: true) + attribute(:title, :string, public?: true) + attribute(:likes, :integer, public?: true) + attribute(:arbitrary_timestamp, :utc_datetime_usec, public?: true) + create_timestamp(:created_at, writable?: true, public?: true) end aggregates do @@ -49,15 +51,22 @@ defmodule AshPostgres.Test.Comment do end relationships do - belongs_to(:post, AshPostgres.Test.Post) - belongs_to(:author, AshPostgres.Test.Author) + belongs_to(:post, AshPostgres.Test.Post) do + public?(true) + end + + belongs_to(:author, AshPostgres.Test.Author) do + public?(true) + end has_many(:ratings, AshPostgres.Test.Rating, + public?: true, destination_attribute: :resource_id, relationship_context: %{data_layer: %{table: "comment_ratings"}} ) has_many(:popular_ratings, AshPostgres.Test.Rating, + public?: true, destination_attribute: :resource_id, relationship_context: %{data_layer: %{table: "comment_ratings"}}, filter: expr(score > 5) diff --git a/test/support/resources/entity.ex b/test/support/resources/entity.ex index e021820a..732f760f 100644 --- a/test/support/resources/entity.ex +++ b/test/support/resources/entity.ex @@ -2,14 +2,15 @@ defmodule AshPostgres.Test.Entity do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer attributes do uuid_primary_key(:id) - attribute(:full_name, :string, allow_nil?: false) + attribute(:full_name, :string, allow_nil?: false, public?: true) - timestamps(private?: false) + timestamps(public?: true) end postgres do @@ -18,6 +19,8 @@ defmodule AshPostgres.Test.Entity do end actions do + default_accept(:*) + defaults([:create, :read]) read :read_from_temp do diff --git a/test/support/resources/integer_post.ex b/test/support/resources/integer_post.ex index 89dc9f43..13b37b4b 100644 --- a/test/support/resources/integer_post.ex +++ b/test/support/resources/integer_post.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.IntegerPost do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer postgres do @@ -9,11 +10,13 @@ defmodule AshPostgres.Test.IntegerPost do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end attributes do integer_primary_key(:id) - attribute(:title, :string) + attribute(:title, :string, public?: true) end end diff --git a/test/support/resources/manager.ex b/test/support/resources/manager.ex index bdfe0709..43105c21 100644 --- a/test/support/resources/manager.ex +++ b/test/support/resources/manager.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.Manager do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer postgres do @@ -9,6 +10,8 @@ defmodule AshPostgres.Test.Manager do end actions do + default_accept(:*) + defaults([:read, :update, :destroy]) create :create do @@ -25,14 +28,15 @@ defmodule AshPostgres.Test.Manager do attributes do uuid_primary_key(:id) - attribute(:name, :string) - attribute(:code, :string, allow_nil?: false) - attribute(:must_be_present, :string, allow_nil?: false) - attribute(:role, :string) + attribute(:name, :string, public?: true) + attribute(:code, :string, allow_nil?: false, public?: true) + attribute(:must_be_present, :string, allow_nil?: false, public?: true) + attribute(:role, :string, public?: true) end relationships do belongs_to :organization, AshPostgres.Test.Organization do + public?(true) attribute_writable?(true) end end diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index ada81e25..6c7346a2 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.Organization do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -24,17 +25,27 @@ defmodule AshPostgres.Test.Organization do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end attributes do uuid_primary_key(:id, writable?: true) - attribute(:name, :string) + attribute(:name, :string, public?: true) end relationships do - has_many(:users, AshPostgres.Test.User) - has_many(:posts, AshPostgres.Test.Post) - has_many(:managers, AshPostgres.Test.Manager) + has_many(:users, AshPostgres.Test.User) do + public?(true) + end + + has_many(:posts, AshPostgres.Test.Post) do + public?(true) + end + + has_many(:managers, AshPostgres.Test.Manager) do + public?(true) + end end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 684a7b92..6c21a51f 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -16,6 +16,7 @@ end defmodule AshPostgres.Test.Post do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -73,7 +74,14 @@ defmodule AshPostgres.Test.Post do end actions do - defaults([:update, :destroy]) + default_accept(:*) + + defaults([:destroy]) + + update :update do + primary?(true) + require_atomic?(false) + end read :title_is_foo do filter(expr(title == "foo")) @@ -113,6 +121,7 @@ defmodule AshPostgres.Test.Post do end update :manual_update do + require_atomic?(false) manual(AshPostgres.Test.Post.ManualUpdate) end end @@ -125,83 +134,103 @@ defmodule AshPostgres.Test.Post do uuid_primary_key(:id, writable?: true) attribute(:title, :string) do + public?(true) source(:title_column) end - attribute(:score, :integer) - attribute(:public, :boolean) - attribute(:category, :ci_string) - attribute(:type, :atom, default: :sponsored, private?: true, writable?: false) - attribute(:price, :integer) - attribute(:decimal, :decimal, default: Decimal.new(0)) - attribute(:status, AshPostgres.Test.Types.Status) - attribute(:status_enum, AshPostgres.Test.Types.StatusEnum) - attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast, source: :status_enum) - attribute(:point, AshPostgres.Test.Point) - attribute(:composite_point, AshPostgres.Test.CompositePoint) - attribute(:stuff, :map) - attribute(:list_of_stuff, {:array, :map}) - attribute(:uniq_one, :string) - attribute(:uniq_two, :string) - attribute(:uniq_custom_one, :string) - attribute(:uniq_custom_two, :string) + attribute(:score, :integer, public?: true) + attribute(:public, :boolean, public?: true) + attribute(:category, :ci_string, public?: true) + attribute(:type, :atom, default: :sponsored, writable?: false, public?: false) + attribute(:price, :integer, public?: true) + attribute(:decimal, :decimal, default: Decimal.new(0), public?: true) + attribute(:status, AshPostgres.Test.Types.Status, public?: true) + attribute(:status_enum, AshPostgres.Test.Types.StatusEnum, public?: true) + + attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast, + source: :status_enum, + public?: true + ) + + attribute(:point, AshPostgres.Test.Point, public?: true) + attribute(:composite_point, AshPostgres.Test.CompositePoint, public?: true) + attribute(:stuff, :map, public?: true) + attribute(:list_of_stuff, {:array, :map}, public?: true) + attribute(:uniq_one, :string, public?: true) + attribute(:uniq_two, :string, public?: true) + attribute(:uniq_custom_one, :string, public?: true) + attribute(:uniq_custom_two, :string, public?: true) attribute :list_containing_nils, {:array, :string} do + public?(true) constraints(nil_items?: true) end - create_timestamp(:created_at) - update_timestamp(:updated_at) + create_timestamp(:created_at, writable?: true, public?: true) + update_timestamp(:updated_at, writable?: true, public?: true) end code_interface do - define_for(AshPostgres.Test.Api) + define(:create, args: [:title]) define(:get_by_id, action: :read, get_by: [:id]) define(:increment_score, args: [{:optional, :amount}]) + define(:destroy) + define(:bulk_create, bulk?: true, action: :create) end relationships do belongs_to :organization, AshPostgres.Test.Organization do + public?(true) attribute_writable?(true) end - belongs_to(:author, AshPostgres.Test.Author) + belongs_to(:author, AshPostgres.Test.Author) do + public?(true) + end has_many :posts_with_matching_title, __MODULE__ do + public?(true) no_attributes?(true) filter(expr(parent(title) == title and parent(id) != id)) end - has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id) + has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id, public?: true) has_many :comments_matching_post_title, AshPostgres.Test.Comment do + public?(true) filter(expr(title == parent_expr(title))) end has_many :popular_comments, AshPostgres.Test.Comment do + public?(true) destination_attribute(:post_id) filter(expr(likes > 10)) end has_many :comments_containing_title, AshPostgres.Test.Comment do + public?(true) manual(AshPostgres.Test.Post.CommentsContainingTitle) end has_many :comments_with_high_rating, AshPostgres.Test.Comment do + public?(true) filter(expr(ratings.score > 5)) end has_many(:ratings, AshPostgres.Test.Rating, + public?: true, destination_attribute: :resource_id, relationship_context: %{data_layer: %{table: "post_ratings"}} ) has_many(:post_links, AshPostgres.Test.PostLink, + public?: true, destination_attribute: :source_post_id, filter: [state: :active] ) many_to_many(:linked_posts, __MODULE__, + public?: true, through: AshPostgres.Test.PostLink, join_relationship: :post_links, source_attribute_on_join_resource: :source_post_id, @@ -209,13 +238,16 @@ defmodule AshPostgres.Test.Post do ) many_to_many(:followers, AshPostgres.Test.User, + public?: true, through: AshPostgres.Test.PostFollower, source_attribute_on_join_resource: :post_id, destination_attribute_on_join_resource: :follower_id, read_action: :active ) - has_many(:views, AshPostgres.Test.PostView) + has_many(:views, AshPostgres.Test.PostView) do + public?(true) + end end validations do @@ -483,10 +515,10 @@ end defmodule CalculatePostPriceString do @moduledoc false - use Ash.Calculation + use Ash.Resource.Calculation @impl true - def select(_, _, _), do: [:price] + def load(_, _, _), do: [:price] @impl true def calculate(records, _, _) do @@ -500,7 +532,7 @@ end defmodule CalculatePostPriceStringWithSymbol do @moduledoc false - use Ash.Calculation + use Ash.Resource.Calculation @impl true def load(_, _, _), do: [:price_string] @@ -524,7 +556,7 @@ defmodule AshPostgres.Test.Post.ManualUpdate do |> Ash.Changeset.for_update(:update, changeset.attributes) |> Ash.Changeset.force_change_attribute(:title, "manual") |> Ash.Changeset.load(:comments) - |> AshPostgres.Test.Api.update!() + |> Ash.update!() } end end diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex index 850c75e5..e60144bf 100644 --- a/test/support/resources/post_follower.ex +++ b/test/support/resources/post_follower.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.PostFollower do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer postgres do @@ -9,6 +10,8 @@ defmodule AshPostgres.Test.PostFollower do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -18,10 +21,12 @@ defmodule AshPostgres.Test.PostFollower do relationships do belongs_to :post, AshPostgres.Test.Post do + public?(true) allow_nil?(false) end belongs_to :follower, AshPostgres.Test.User do + public?(true) allow_nil?(false) end end diff --git a/test/support/resources/post_link.ex b/test/support/resources/post_link.ex index 22330d17..910486b2 100644 --- a/test/support/resources/post_link.ex +++ b/test/support/resources/post_link.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.PostLink do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer postgres do @@ -9,6 +10,8 @@ defmodule AshPostgres.Test.PostLink do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end @@ -18,6 +21,7 @@ defmodule AshPostgres.Test.PostLink do attributes do attribute :state, :atom do + public?(true) constraints(one_of: [:active, :archived]) default(:active) end @@ -25,11 +29,13 @@ defmodule AshPostgres.Test.PostLink do relationships do belongs_to :source_post, AshPostgres.Test.Post do + public?(true) allow_nil?(false) primary_key?(true) end belongs_to :destination_post, AshPostgres.Test.Post do + public?(true) allow_nil?(false) primary_key?(true) end diff --git a/test/support/resources/post_views.ex b/test/support/resources/post_views.ex index 31bedeff..929fbb45 100644 --- a/test/support/resources/post_views.ex +++ b/test/support/resources/post_views.ex @@ -1,18 +1,23 @@ defmodule AshPostgres.Test.PostView do @moduledoc false - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer actions do + default_accept(:*) + defaults([:create, :read]) end attributes do create_timestamp(:time) - attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]]) + attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]], public?: true) end relationships do belongs_to :post, AshPostgres.Test.Post do + public?(true) allow_nil?(false) attribute_writable?(true) end diff --git a/test/support/resources/profile.ex b/test/support/resources/profile.ex index 33e5395f..0455f9b9 100644 --- a/test/support/resources/profile.ex +++ b/test/support/resources/profile.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.Profile do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer postgres do @@ -11,14 +12,18 @@ defmodule AshPostgres.Test.Profile do attributes do uuid_primary_key(:id, writable?: true) - attribute(:description, :string) + attribute(:description, :string, public?: true) end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end relationships do - belongs_to(:author, AshPostgres.Test.Author) + belongs_to(:author, AshPostgres.Test.Author) do + public?(true) + end end end diff --git a/test/support/resources/rating.ex b/test/support/resources/rating.ex index e0b87fcb..0006ea01 100644 --- a/test/support/resources/rating.ex +++ b/test/support/resources/rating.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.Rating do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer postgres do @@ -9,12 +10,14 @@ defmodule AshPostgres.Test.Rating do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end attributes do uuid_primary_key(:id) - attribute(:score, :integer) - attribute(:resource_id, :uuid) + attribute(:score, :integer, public?: true) + attribute(:resource_id, :uuid, public?: true) end end diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index 72bcbbe2..19a5aa76 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -2,20 +2,22 @@ defmodule AshPostgres.Test.Record do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer attributes do uuid_primary_key(:id) - attribute(:full_name, :string, allow_nil?: false) + attribute(:full_name, :string, allow_nil?: false, public?: true) - timestamps(private?: false) + timestamps(public?: true) end relationships do alias AshPostgres.Test.Entity has_one :entity, Entity do + public?(true) no_attributes?(true) read_action(:read_from_temp) @@ -30,6 +32,8 @@ defmodule AshPostgres.Test.Record do end actions do + default_accept(:*) + defaults([:create, :read]) end end diff --git a/test/support/resources/subquery/access.ex b/test/support/resources/subquery/access.ex index cc585e92..a3df6531 100644 --- a/test/support/resources/subquery/access.ex +++ b/test/support/resources/subquery/access.ex @@ -3,6 +3,7 @@ defmodule AshPostgres.Test.Subquery.Access do alias AshPostgres.Test.Subquery.Parent use Ash.Resource, + domain: AshPostgres.Test.Subquery.ParentDomain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -17,19 +18,19 @@ defmodule AshPostgres.Test.Subquery.Access do attributes do uuid_primary_key(:id) - attribute(:parent_id, :uuid) - attribute(:email, :string) + attribute(:parent_id, :uuid, public?: true) + attribute(:email, :string, public?: true) end code_interface do - define_for(AshPostgres.Test.Subquery.ParentApi) - define(:create) define(:read) end relationships do - belongs_to(:parent, Parent) + belongs_to(:parent, Parent) do + public?(true) + end end policies do @@ -39,6 +40,8 @@ defmodule AshPostgres.Test.Subquery.Access do end actions do + default_accept(:*) + defaults([:create, :update, :destroy]) read :read do diff --git a/test/support/resources/subquery/child.ex b/test/support/resources/subquery/child.ex index a85500de..cf089bd5 100644 --- a/test/support/resources/subquery/child.ex +++ b/test/support/resources/subquery/child.ex @@ -3,6 +3,7 @@ defmodule AshPostgres.Test.Subquery.Child do alias AshPostgres.Test.Subquery.Through use Ash.Resource, + domain: AshPostgres.Test.Subquery.ChildDomain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -15,18 +16,17 @@ defmodule AshPostgres.Test.Subquery.Child do attributes do uuid_primary_key(:id) - attribute(:state, :string) + attribute(:state, :string, public?: true) end code_interface do - define_for(AshPostgres.Test.Subquery.ChildApi) - define(:create) define(:read) end relationships do has_many :throughs, Through do + public?(true) source_attribute(:id) destination_attribute(:child_id) end @@ -48,6 +48,8 @@ defmodule AshPostgres.Test.Subquery.Child do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end end diff --git a/test/support/resources/subquery/child_api.ex b/test/support/resources/subquery/child_domain.ex similarity index 57% rename from test/support/resources/subquery/child_api.ex rename to test/support/resources/subquery/child_domain.ex index 320dc709..7e427331 100644 --- a/test/support/resources/subquery/child_api.ex +++ b/test/support/resources/subquery/child_domain.ex @@ -1,11 +1,15 @@ -defmodule AshPostgres.Test.Subquery.ChildApi do +defmodule AshPostgres.Test.Subquery.ChildDomain do @moduledoc false alias AshPostgres.Test.Subquery.Child alias AshPostgres.Test.Subquery.Through - use Ash.Api + use Ash.Domain resources do resource(Child) resource(Through) end + + authorization do + authorize(:when_requested) + end end diff --git a/test/support/resources/subquery/parent.ex b/test/support/resources/subquery/parent.ex index 33421c32..9de04b9f 100644 --- a/test/support/resources/subquery/parent.ex +++ b/test/support/resources/subquery/parent.ex @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.Subquery.Parent do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Subquery.ParentDomain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -15,22 +16,25 @@ defmodule AshPostgres.Test.Subquery.Parent do attributes do uuid_primary_key(:id) - attribute(:owner_email, :string) - attribute(:other_owner_email, :string) - attribute(:visible, :boolean) + attribute(:owner_email, :string, public?: true) + attribute(:other_owner_email, :string, public?: true) + attribute(:visible, :boolean, public?: true) end relationships do many_to_many :children, Child do + public?(true) through(Through) source_attribute(:id) source_attribute_on_join_resource(:parent_id) destination_attribute(:id) destination_attribute_on_join_resource(:child_id) - api(AshPostgres.Test.Subquery.ChildApi) + domain(AshPostgres.Test.Subquery.ChildDomain) end - has_many(:accesses, Access) + has_many(:accesses, Access) do + public?(true) + end end policies do @@ -48,8 +52,6 @@ defmodule AshPostgres.Test.Subquery.Parent do end code_interface do - define_for(AshPostgres.Test.Subquery.ParentApi) - define(:create) define(:read) @@ -57,6 +59,8 @@ defmodule AshPostgres.Test.Subquery.Parent do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end end diff --git a/test/support/resources/subquery/parent_api.ex b/test/support/resources/subquery/parent_domain.ex similarity index 57% rename from test/support/resources/subquery/parent_api.ex rename to test/support/resources/subquery/parent_domain.ex index 0bb99db0..9d73f6af 100644 --- a/test/support/resources/subquery/parent_api.ex +++ b/test/support/resources/subquery/parent_domain.ex @@ -1,11 +1,15 @@ -defmodule AshPostgres.Test.Subquery.ParentApi do +defmodule AshPostgres.Test.Subquery.ParentDomain do @moduledoc false alias AshPostgres.Test.Subquery.Access alias AshPostgres.Test.Subquery.Parent - use Ash.Api + use Ash.Domain resources do resource(Parent) resource(Access) end + + authorization do + authorize(:when_requested) + end end diff --git a/test/support/resources/subquery/through.ex b/test/support/resources/subquery/through.ex index cc1cfe01..7f1e96e9 100644 --- a/test/support/resources/subquery/through.ex +++ b/test/support/resources/subquery/through.ex @@ -2,9 +2,10 @@ defmodule AshPostgres.Test.Subquery.Through do @moduledoc false alias AshPostgres.Test.Subquery.Child alias AshPostgres.Test.Subquery.Parent - alias AshPostgres.Test.Subquery.ParentApi + alias AshPostgres.Test.Subquery.ParentDomain use Ash.Resource, + domain: AshPostgres.Test.Subquery.ChildDomain, data_layer: AshPostgres.DataLayer, authorizers: [ Ash.Policy.Authorizer @@ -17,29 +18,31 @@ defmodule AshPostgres.Test.Subquery.Through do attributes do attribute :parent_id, :uuid do + public?(true) primary_key?(true) allow_nil?(false) end attribute :child_id, :uuid do + public?(true) primary_key?(true) allow_nil?(false) end end code_interface do - define_for(AshPostgres.Test.Subquery.ChildApi) - define(:create) define(:read) end relationships do belongs_to :parent, Parent do - api(ParentApi) + public?(true) + domain(ParentDomain) end belongs_to :child, Child do + public?(true) source_attribute(:parent_id) destination_attribute(:id) end @@ -52,6 +55,8 @@ defmodule AshPostgres.Test.Subquery.Through do end actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) end end diff --git a/test/support/resources/temp_entity.ex b/test/support/resources/temp_entity.ex index 0b5ba447..eb29bf1c 100644 --- a/test/support/resources/temp_entity.ex +++ b/test/support/resources/temp_entity.ex @@ -2,14 +2,15 @@ defmodule AshPostgres.Test.TempEntity do @moduledoc false use Ash.Resource, + domain: AshPostgres.Test.Domain, data_layer: AshPostgres.DataLayer attributes do uuid_primary_key(:id) - attribute(:full_name, :string, allow_nil?: false) + attribute(:full_name, :string, allow_nil?: false, public?: true) - timestamps(private?: false) + timestamps(public?: true) end postgres do @@ -19,6 +20,8 @@ defmodule AshPostgres.Test.TempEntity do end actions do + default_accept(:*) + defaults([:create, :read]) end end diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index 453ae319..f416eb98 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -1,8 +1,12 @@ defmodule AshPostgres.Test.User do @moduledoc false - use Ash.Resource, data_layer: AshPostgres.DataLayer + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) read :active do @@ -16,8 +20,8 @@ defmodule AshPostgres.Test.User do attributes do uuid_primary_key(:id) - attribute(:is_active, :boolean) - attribute(:name, :string) + attribute(:is_active, :boolean, public?: true) + attribute(:name, :string, public?: true) end postgres do @@ -27,9 +31,12 @@ defmodule AshPostgres.Test.User do relationships do belongs_to :organization, AshPostgres.Test.Organization do + public?(true) attribute_writable?(true) end - has_many(:accounts, AshPostgres.Test.Account) + has_many(:accounts, AshPostgres.Test.Account) do + public?(true) + end end end diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index 1fd79f6e..81fef08f 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -7,6 +7,10 @@ defmodule AshPostgres.TestNoSandboxRepo do send(self(), data) end + def pg_version do + Version.parse!(System.get_env("PG_VERSION") || "16.0.0") + end + def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) @@ -16,7 +20,7 @@ defmodule AshPostgres.TestNoSandboxRepo do Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) AshPostgres.MultitenancyTest.Org - |> AshPostgres.MultitenancyTest.Api.read!() + |> Ash.read!() |> Enum.map(&"org_#{&1.id}") end end diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 84781c6a..07b29700 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -7,6 +7,10 @@ defmodule AshPostgres.TestRepo do send(self(), data) end + def pg_version do + Version.parse!(System.get_env("PG_VERSION") || "16.0.0") + end + def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) @@ -16,7 +20,7 @@ defmodule AshPostgres.TestRepo do Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) AshPostgres.MultitenancyTest.Org - |> AshPostgres.MultitenancyTest.Api.read!() + |> Ash.read!() |> Enum.map(&"org_#{&1.id}") end end diff --git a/test/support/types/money.ex b/test/support/types/money.ex index fde16df5..c1569be4 100644 --- a/test/support/types/money.ex +++ b/test/support/types/money.ex @@ -5,11 +5,13 @@ defmodule AshPostgres.Test.Money do attributes do attribute :amount, :integer do + public?(true) allow_nil?(false) constraints(min: 0) end attribute :currency, :atom do + public?(true) constraints(one_of: [:eur, :usd]) end end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 60f9551f..603fe0df 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.TransactionTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query @@ -12,7 +12,7 @@ defmodule AshPostgres.Test.TransactionTest do raise "something bad happened" end) |> send_after_transaction_result() - |> Api.create() + |> Ash.create() end assert_receive {:error, @@ -32,12 +32,12 @@ defmodule AshPostgres.Test.TransactionTest do raise "something bad happened inside" end) |> send_after_transaction_result() - |> Api.create!() + |> Ash.create!() {:ok, result} end) |> send_after_transaction_result() - |> Api.create() + |> Ash.create() end assert_receive {:error, @@ -52,7 +52,7 @@ defmodule AshPostgres.Test.TransactionTest do Post |> Ash.Changeset.for_create(:create) |> send_after_transaction_result() - |> Api.create() + |> Ash.create() assert_receive {:ok, %Post{}} end @@ -68,17 +68,17 @@ defmodule AshPostgres.Test.TransactionTest do raise "something bad happened inside" end) |> send_after_transaction_result() - |> Api.create!() + |> Ash.create!() {:ok, result} end) |> Ash.Changeset.after_transaction(fn _changeset, {:error, _} -> Post |> Ash.Changeset.for_create(:create) - |> Api.create() + |> Ash.create() end) |> send_after_transaction_result() - |> Api.create() + |> Ash.create() assert_receive {:error, %RuntimeError{ diff --git a/test/type_test.exs b/test/type_test.exs index d81bba1c..53c073e4 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -1,31 +1,31 @@ defmodule AshPostgres.Test.TypeTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "complex custom types can be used" do post = Post - |> Ash.Changeset.new(%{title: "title", point: {1.0, 2.0, 3.0}}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", point: {1.0, 2.0, 3.0}}) + |> Ash.create!() assert post.point == {1.0, 2.0, 3.0} end test "complex custom types can be accessed with fragments" do Post - |> Ash.Changeset.new(%{title: "title", point: {1.0, 2.0, 3.0}}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", point: {1.0, 2.0, 3.0}}) + |> Ash.create!() Post - |> Ash.Changeset.new(%{title: "title", point: {2.0, 1.0, 3.0}}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title", point: {2.0, 1.0, 3.0}}) + |> Ash.create!() assert [%{point: {2.0, 1.0, 3.0}}] = Post |> Ash.Query.filter(fragment("(?)[1] > (?)[2]", point, point)) - |> Api.read!() + |> Ash.read!() end test "uuids can be used as strings in fragments" do @@ -33,6 +33,6 @@ defmodule AshPostgres.Test.TypeTest do Post |> Ash.Query.filter(fragment("? = ?", id, type(^uuid, :uuid))) - |> Api.read!() + |> Ash.read!() end end diff --git a/test/unique_identity_test.exs b/test/unique_identity_test.exs index 51557855..e2d1f9a4 100644 --- a/test/unique_identity_test.exs +++ b/test/unique_identity_test.exs @@ -1,34 +1,43 @@ defmodule AshPostgres.Test.UniqueIdentityTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query test "unique constraint errors are properly caught" do post = Post - |> Ash.Changeset.new(%{title: "title"}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/Invalid value provided for id: has already been taken/, fn -> Post - |> Ash.Changeset.new(%{id: post.id}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{id: post.id}) + |> Ash.create!() end end test "a unique constraint can be used to upsert when the resource has a base filter" do post = Post - |> Ash.Changeset.new(%{title: "title", uniq_one: "fred", uniq_two: "astair", price: 10}) - |> Api.create!() + |> Ash.Changeset.for_create(:create, %{ + title: "title", + uniq_one: "fred", + uniq_two: "astair", + price: 10 + }) + |> Ash.create!() new_post = Post - |> Ash.Changeset.new(%{title: "title2", uniq_one: "fred", uniq_two: "astair"}) - |> Api.create!(upsert?: true, upsert_identity: :uniq_one_and_two) + |> Ash.Changeset.for_create(:create, %{ + title: "title2", + uniq_one: "fred", + uniq_two: "astair" + }) + |> Ash.create!(upsert?: true, upsert_identity: :uniq_one_and_two) assert new_post.id == post.id assert new_post.price == 10 diff --git a/test/upsert_test.exs b/test/upsert_test.exs index a6b8195f..ca29bad6 100644 --- a/test/upsert_test.exs +++ b/test/upsert_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.UpsertTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Post} + alias AshPostgres.Test.Post require Ash.Query @@ -13,7 +13,7 @@ defmodule AshPostgres.Test.UpsertTest do id: id, title: "title2" }) - |> Api.create!(upsert?: true) + |> Ash.create!(upsert?: true) assert new_post.id == id assert new_post.created_at == new_post.updated_at @@ -24,7 +24,7 @@ defmodule AshPostgres.Test.UpsertTest do id: id, title: "title2" }) - |> Api.create!(upsert?: true) + |> Ash.create!(upsert?: true) assert updated_post.id == id assert updated_post.created_at == new_post.created_at @@ -40,7 +40,7 @@ defmodule AshPostgres.Test.UpsertTest do id: id, title: "title2" }) - |> Api.create!(upsert?: true) + |> Ash.create!(upsert?: true) assert new_post.id == id assert new_post.created_at == new_post.updated_at @@ -52,7 +52,7 @@ defmodule AshPostgres.Test.UpsertTest do title: "title2", decimal: Decimal.new(5) }) - |> Api.create!(upsert?: true) + |> Ash.create!(upsert?: true) assert updated_post.id == id assert Decimal.equal?(updated_post.decimal, Decimal.new(5)) diff --git a/test_snapshot_path/extensions.json b/test_snapshot_path/extensions.json deleted file mode 100644 index e084bbff..00000000 --- a/test_snapshot_path/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "installed": [ - "ash-functions", - "uuid-ossp", - "pg_trgm", - "citext", - "demo-functions_v1" - ], - "ash_functions_version": 3 -} \ No newline at end of file From 3ad3f5f5d6052433746691ccee501ab30823acaf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:03:16 -0400 Subject: [PATCH 0312/1215] chore: update ash, resources and an error message --- lib/verifiers/verify_postgres_version.ex | 2 +- mix.exs | 2 +- mix.lock | 2 +- test/support/resources/post.ex | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/verifiers/verify_postgres_version.ex b/lib/verifiers/verify_postgres_version.ex index 0c7bad9d..4b36dc81 100644 --- a/lib/verifiers/verify_postgres_version.ex +++ b/lib/verifiers/verify_postgres_version.ex @@ -14,7 +14,7 @@ defmodule AshPostgres.Verifiers.VerifyPostgresVersion do if Version.match?(read_version, ">= 14.0.0") && Version.match?(mutation_version, ">= 14.0.0") do :ok else - {:error, "AshPostgres now only supports versions >= 14.0."} + {:error, "AshPostgres only supports postgres versions >= 14.0."} end end diff --git a/mix.exs b/mix.exs index 7df3a322..d40895dd 100644 --- a/mix.exs +++ b/mix.exs @@ -159,7 +159,7 @@ defmodule AshPostgres.MixProject do {:spark, path: "../spark", override: true}, # dev/test dependencies {:simple_sat, "~> 0.1"}, - {:ash, ash_version(github: "ash-project/ash", branch: "3.0")}, + {:ash, ash_version("~> 3.0.0-rc.0")}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index da3f1f08..64f33420 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "37587cbc580c30248044eea01d461f578df6cbc4", [branch: "3.0"]}, + "ash": {:hex, :ash, "3.0.0-rc.0", "5acbfff801258624320dad950b07ea20ac6d8fe06a197d96c806d0bc5567c1b1", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e0ff1ba71b7096480da0a1472b95de7b73b88971eeb78a20779ed2bbba532df8"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6c21a51f..371f8497 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -175,7 +175,6 @@ defmodule AshPostgres.Test.Post do define(:get_by_id, action: :read, get_by: [:id]) define(:increment_score, args: [{:optional, :amount}]) define(:destroy) - define(:bulk_create, bulk?: true, action: :create) end relationships do From b66427e9bd29d3ee12f9590b494adb1612c92cf9 Mon Sep 17 00:00:00 2001 From: bcksl <121328003+bcksl@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:05:14 +0200 Subject: [PATCH 0313/1215] feat: add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) Co-authored-by: Zach Daniel --- lib/mix/tasks/ash_postgres.create.ex | 4 +++- lib/mix/tasks/ash_postgres.drop.ex | 4 +++- lib/repo.ex | 11 ++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index e4de6344..9ac3e9f4 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -34,7 +34,9 @@ defmodule Mix.Tasks.AshPostgres.Create do def run(args) do {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) - repos = AshPostgres.MixHelpers.repos!(opts, args) + repos = + AshPostgres.MixHelpers.repos!(opts, args) + |> Enum.filter(fn repo -> repo.create? end) repo_args = Enum.flat_map(repos, fn repo -> diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index b8ac91de..16c3777c 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -44,7 +44,9 @@ defmodule Mix.Tasks.AshPostgres.Drop do {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) opts = Keyword.merge(@default_opts, opts) - repos = AshPostgres.MixHelpers.repos!(opts, args) + repos = + AshPostgres.MixHelpers.repos!(opts, args) + |> Enum.filter(fn repo -> repo.drop? end) repo_args = Enum.flat_map(repos, fn repo -> diff --git a/lib/repo.ex b/lib/repo.ex index 78e3bb8b..6437158c 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -71,6 +71,10 @@ defmodule AshPostgres.Repo do @doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields" @callback override_migration_type(atom) :: atom + @doc "Should the repo should be created by `mix ash_postgres.create`?" + @callback create?() :: boolean + @doc "Should the repo should be dropped by `mix ash_postgres.drop`?" + @callback drop?() :: boolean defmacro __using__(opts) do quote bind_quoted: [opts: opts] do @@ -91,6 +95,9 @@ defmodule AshPostgres.Repo do def migrations_path, do: nil def default_prefix, do: "public" def override_migration_type(type), do: type + def create?, do: true + def drop?, do: true + def transaction!(fun) do case fun.() do @@ -226,7 +233,9 @@ defmodule AshPostgres.Repo do all_tenants: 0, tenant_migrations_path: 0, default_prefix: 0, - override_migration_type: 1 + override_migration_type: 1, + create?: 0, + drop?: 0 end end end From c436973e9ff08f5dfa5641fc8fa05c3adc6c5b21 Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Thu, 28 Mar 2024 05:07:33 +0800 Subject: [PATCH 0314/1215] fix!: Use UTC for default generated timestamps (#131) In cases where the database server is not set to UTC, this fixes timezone inconsistencies when adding timestamp columns to tables with existing data, that would then have their timestamps set to non-UTC times. --- lib/migration_generator/migration_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 1d2c9796..7d398543 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2823,7 +2823,7 @@ defmodule AshPostgres.MigrationGenerator do ~S[fragment("uuid_generate_v4()")] default == (&DateTime.utc_now/0) -> - ~S[fragment("now()")] + ~S[fragment("(now() AT TIME ZONE 'utc')")] default == (&Date.utc_today/0) -> ~S[fragment("CURRENT_DATE")] From 804482b8ac7e6ec0fae695e29e97b04871b00229 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:20:07 -0400 Subject: [PATCH 0315/1215] improvement!: change defaults for uuids to `gen_random_uuid()` --- documentation/how_to/upgrade.md | 17 + .../migration_generator.ex | 6 +- .../test_repo/accounts/20240327211150.json | 67 ++++ .../test_repo/authors/20240327211150.json | 72 ++++ .../comment_ratings/20240327211150.json | 67 ++++ .../test_repo/comments/20240327211150.json | 125 +++++++ .../test_repo/comments/20240327211917.json | 125 +++++++ .../20240327211150.json | 29 ++ .../20240327211150.json | 105 ++++++ .../20240327211917.json | 105 ++++++ .../20240327211150.json | 49 +++ .../20240327211917.json | 49 +++ .../20240327211150.json | 97 +++++ .../20240327211917.json | 97 +++++ .../20240327211150.json | 67 ++++ .../test_repo/entities/20240327211150.json | 59 +++ .../test_repo/entities/20240327211917.json | 59 +++ .../test_repo/managers/20240327211150.json | 107 ++++++ .../multitenant_orgs/20240327211150.json | 49 +++ .../test_repo/orgs/20240327211150.json | 39 ++ .../post_followers/20240327211150.json | 85 +++++ .../post_ratings/20240327211150.json | 67 ++++ .../test_repo/post_views/20240327211917.json | 49 +++ .../test_repo/posts/20240327211150.json | 342 ++++++++++++++++++ .../test_repo/posts/20240327211917.json | 342 ++++++++++++++++++ .../profiles.profile/20240327211150.json | 67 ++++ .../test_repo/records/20240327211150.json | 59 +++ .../test_repo/records/20240327211917.json | 59 +++ .../temp.temp_entities/20240327211150.json | 59 +++ .../temp.temp_entities/20240327211917.json | 59 +++ .../multitenant_posts/20240327211149.json | 95 +++++ .../test_repo/users/20240327211150.json | 105 ++++++ .../20240327211150_migrate_resources19.exs | 209 +++++++++++ .../20240327211917_migrate_resources20.exs | 99 +++++ .../20240327211149_migrate_resources2.exs | 21 ++ 35 files changed, 3104 insertions(+), 3 deletions(-) create mode 100644 documentation/how_to/upgrade.md create mode 100644 priv/resource_snapshots/test_repo/accounts/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/authors/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/comments/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/comments/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/entities/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/entities/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/managers/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/orgs/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/post_ratings/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/post_views/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/posts/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/posts/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/records/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/records/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json create mode 100644 priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json create mode 100644 priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json create mode 100644 priv/resource_snapshots/test_repo/users/20240327211150.json create mode 100644 priv/test_repo/migrations/20240327211150_migrate_resources19.exs create mode 100644 priv/test_repo/migrations/20240327211917_migrate_resources20.exs create mode 100644 priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs diff --git a/documentation/how_to/upgrade.md b/documentation/how_to/upgrade.md new file mode 100644 index 00000000..88d8675b --- /dev/null +++ b/documentation/how_to/upgrade.md @@ -0,0 +1,17 @@ +# Upgrade from 2.0 to 3.0 + +There are only three breaking changes in this release, one of them is very significant, the other two are minor. + +# AshPostgres officially supports only postgresql version 14 or higher + +While _most_ things will work with versions as low as 11, we are relying on features of newer postgres versions. We will not be testing against versions lower than 14, and we will not be supporting them. If you are using an older version of postgres, you will need to upgrade. + +If you _must_ use an older version, the only thing that you'll need to change in the short term is to handle the fact that we now use `gen_random_uuid()` as the default for generated uuids (see below), which is only available after postgres _13_. Additionally, if you are on postgres 12 or earlier, you will need to replace `ANYCOMPATIBLE` with `ANYELEMENT` in the `ash-functions` extension migration. + +## `gen_random_uuid()` is now the default for generated uuids + +In the past, we used `uuid_generate_v4()` as the default for generated uuids. This function is part of the `uuid-ossp` extension, which is not installed by default in postgres. `gen_random_uuid()` is a built-in function that is available in all versions of postgres 13 and higher. If you are using an older version of postgres, you will need to install the `uuid-ossp` extension and change the default in your migrations. + +## utc datetimes that default to `&DateTime.now/0` are now cast to `UTC` + +This is a layer of safety to ensure consistency in the default values of a database and the datetimes that are sent to/from the database. When you generate migrations you will notice your timestamps change from defaulting to `now()` in your migrations to `now() AT TIMESTAMP 'utc'`. You are free to undo this change, by setting `migration_defaults` in your resource, or simply by deleting the generated migration. diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7d398543..2f0cc5de 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2816,11 +2816,11 @@ defmodule AshPostgres.MigrationGenerator do @uuid_functions [&Ash.UUID.generate/0, &Ecto.UUID.generate/0] - defp default(%{name: name, default: default}, resource, repo) when is_function(default) do + defp default(%{name: name, default: default}, resource, _repo) when is_function(default) do configured_default(resource, name) || cond do - default in @uuid_functions && "uuid-ossp" in (repo.config()[:installed_extensions] || []) -> - ~S[fragment("uuid_generate_v4()")] + default in @uuid_functions -> + ~S[fragment("gen_random_uuid()")] default == (&DateTime.utc_now/0) -> ~S[fragment("(now() AT TIME ZONE 'utc')")] diff --git a/priv/resource_snapshots/test_repo/accounts/20240327211150.json b/priv/resource_snapshots/test_repo/accounts/20240327211150.json new file mode 100644 index 00000000..9062e475 --- /dev/null +++ b/priv/resource_snapshots/test_repo/accounts/20240327211150.json @@ -0,0 +1,67 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "is_active", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "accounts_user_id_fkey", + "table": "users", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "accounts", + "hash": "91E32D9EB389743A8F1C9CC024CD8E0CA45EE5DDB37484B324E0ACFA54E89784", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/authors/20240327211150.json b/priv/resource_snapshots/test_repo/authors/20240327211150.json new file mode 100644 index 00000000..b9242fce --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20240327211150.json @@ -0,0 +1,72 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "first_name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "last_name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "bio", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "badges", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "authors", + "hash": "DA8EA3017418D8D188C05FEFB74864CEE8B3943341B2A9F2178A322694AF81E7", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json b/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json new file mode 100644 index 00000000..0276d34e --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json @@ -0,0 +1,67 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "resource_id", + "references": { + "name": "comment_ratings_resource_id_fkey", + "table": "comments", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": null, + "deferrable": false, + "destination_attribute_default": "fragment(\"gen_random_uuid()\")", + "destination_attribute_generated": false, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "comment_ratings", + "hash": "F5F7409C3174AEDA4167846E09BF6F8BC65F84BAEAE72398854734449437956A", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/comments/20240327211150.json b/priv/resource_snapshots/test_repo/comments/20240327211150.json new file mode 100644 index 00000000..5f5ee9fb --- /dev/null +++ b/priv/resource_snapshots/test_repo/comments/20240327211150.json @@ -0,0 +1,125 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "likes", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "arbitrary_timestamp", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "special_name_fkey", + "table": "posts", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": "delete", + "on_update": "update", + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "comments_author_id_fkey", + "table": "authors", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "comments", + "hash": "D1DFF344AC9FD8F71978647814798F08C614BAB6AE07BC8B2BAEBB0FA050F624", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/comments/20240327211917.json b/priv/resource_snapshots/test_repo/comments/20240327211917.json new file mode 100644 index 00000000..ba814959 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comments/20240327211917.json @@ -0,0 +1,125 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "likes", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "arbitrary_timestamp", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "special_name_fkey", + "table": "posts", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": "delete", + "on_update": "update", + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "comments_author_id_fkey", + "table": "authors", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "comments", + "hash": "44CDB628468EA313E2D70189D0F5F6E6318886DB0F20C5CE6E05D925F92D1342", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json new file mode 100644 index 00000000..2abc38c9 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json @@ -0,0 +1,29 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + } + ], + "table": "complex_calculations_certifications", + "hash": "EAD1F09DA125BB08FDB1186B925BCD84F0DAD8B7C996318E30B3904A7F96EE8D", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json new file mode 100644 index 00000000..43d2092a --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json @@ -0,0 +1,105 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "complex_calculations_certifications_channel_members_user_id_fkey", + "table": "users", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "channel_id", + "references": { + "name": "complex_calculations_certifications_channel_members_channel_id_fkey", + "table": "complex_calculations_channels", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "complex_calculations_certifications_channel_members", + "hash": "FE83A946EF1F2BE71039C4FA23434AD0CF63AB5FF19427353B76FE324C7E7261", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json new file mode 100644 index 00000000..c0c63a06 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json @@ -0,0 +1,105 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "complex_calculations_certifications_channel_members_user_id_fkey", + "table": "users", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "channel_id", + "references": { + "name": "complex_calculations_certifications_channel_members_channel_id_fkey", + "table": "complex_calculations_channels", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "complex_calculations_certifications_channel_members", + "hash": "AA00163B97E1B6DB621688CDF33EC74878BA28102A5D6B278973F55987B7DB34", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json new file mode 100644 index 00000000..b1019014 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "complex_calculations_channels", + "hash": "3BEBD2B361ED4CA69120920B131113996BF5530E24952310AB88E49A79906943", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json new file mode 100644 index 00000000..bc14eec9 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "complex_calculations_channels", + "hash": "660EEED8929709A3B61F1C8C6804305240DE60664B7BE05639869C7E862AFE6F", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json new file mode 100644 index 00000000..c43fecfe --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json @@ -0,0 +1,97 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "documented_at", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "skill_id", + "references": { + "name": "complex_calculations_documentations_skill_id_fkey", + "table": "complex_calculations_skills", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "complex_calculations_documentations", + "hash": "F38F4D9461F1C7CF89A978E50032F68A69874BC69C37CD1CCCE561B08CA9BEC1", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json new file mode 100644 index 00000000..c52e2328 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json @@ -0,0 +1,97 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "documented_at", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "skill_id", + "references": { + "name": "complex_calculations_documentations_skill_id_fkey", + "table": "complex_calculations_skills", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "complex_calculations_documentations", + "hash": "A7F66DE054E88AC2C1975380748C45A1584108E616E8F9B1FF66BD694D34CC96", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json b/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json new file mode 100644 index 00000000..74e0f0b2 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json @@ -0,0 +1,67 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "false", + "size": null, + "type": "boolean", + "source": "removed", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "certification_id", + "references": { + "name": "complex_calculations_skills_certification_id_fkey", + "table": "complex_calculations_certifications", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "complex_calculations_skills", + "hash": "94D2C1A987E08BE36ED2CD4CF8EDAE8476E5CCE71BF4EBBBF51F2B42B8F42816", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/entities/20240327211150.json b/priv/resource_snapshots/test_repo/entities/20240327211150.json new file mode 100644 index 00000000..3598bb33 --- /dev/null +++ b/priv/resource_snapshots/test_repo/entities/20240327211150.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "entities", + "hash": "C996CEB931DB10DD0F1FA9FD70A309242B8A8CBC689E39901497AE8B17261DE7", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/entities/20240327211917.json b/priv/resource_snapshots/test_repo/entities/20240327211917.json new file mode 100644 index 00000000..d187d037 --- /dev/null +++ b/priv/resource_snapshots/test_repo/entities/20240327211917.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "entities", + "hash": "F22199F8D6AFDC5F8565062FA3E939A434105F3AB9415B34800C35157715F147", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/managers/20240327211150.json b/priv/resource_snapshots/test_repo/managers/20240327211150.json new file mode 100644 index 00000000..75e7792c --- /dev/null +++ b/priv/resource_snapshots/test_repo/managers/20240327211150.json @@ -0,0 +1,107 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "code", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "must_be_present", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "role", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "managers_organization_id_fkey", + "table": "orgs", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "managers", + "hash": "17D7508E7D328026B42E37C76865A1FA6C2BCBCEEF745358769A19A39C71C61F", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [ + { + "name": "uniq_code", + "keys": [ + "code" + ], + "base_filter": null, + "all_tenants?": false, + "index_name": "managers_uniq_code_index" + } + ], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json b/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json new file mode 100644 index 00000000..98d3c1a9 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "multitenant_orgs", + "hash": "8272A81AAF88BCC843DCB78558ECE612D31CD6E3FFB0AEA17A94B0223A4815BB", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": true, + "attribute": "id", + "strategy": "attribute" + }, + "schema": null, + "identities": [ + { + "name": "unique_by_name", + "keys": [ + "name" + ], + "base_filter": null, + "all_tenants?": false, + "index_name": "multitenant_orgs_unique_by_name_index" + } + ], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/orgs/20240327211150.json b/priv/resource_snapshots/test_repo/orgs/20240327211150.json new file mode 100644 index 00000000..c05b8c34 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orgs/20240327211150.json @@ -0,0 +1,39 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "orgs", + "hash": "964A05962CF0871AF3C96713E799037155149E5E69FAB9A85BD4449F2A075906", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/post_followers/20240327211150.json b/priv/resource_snapshots/test_repo/post_followers/20240327211150.json new file mode 100644 index 00000000..c4dde8ca --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240327211150.json @@ -0,0 +1,85 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "post_followers_post_id_fkey", + "table": "posts", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "follower_id", + "references": { + "name": "post_followers_follower_id_fkey", + "table": "users", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "post_followers", + "hash": "0996CE6B4B7B42D124DCCE64AAC44D0E41FD5B877F27F38B703FD62BDAC14520", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json b/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json new file mode 100644 index 00000000..5d9a9998 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json @@ -0,0 +1,67 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "resource_id", + "references": { + "name": "post_ratings_resource_id_fkey", + "table": "posts", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": null, + "deferrable": false, + "destination_attribute_default": "fragment(\"gen_random_uuid()\")", + "destination_attribute_generated": false, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "post_ratings", + "hash": "251643EE9B79229CEB3A6CEB2CCAEF67E02A5017D780607E6AA87C016F9D5366", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/post_views/20240327211917.json b/priv/resource_snapshots/test_repo/post_views/20240327211917.json new file mode 100644 index 00000000..a362c0fc --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_views/20240327211917.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "time", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "browser", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "post_views", + "hash": "3DC29B11C7D71D2C29C9FE68F52664F244D6DF2F4372A9F6BD135BD89CAD10B4", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20240327211150.json b/priv/resource_snapshots/test_repo/posts/20240327211150.json new file mode 100644 index 00000000..952c73f6 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240327211150.json @@ -0,0 +1,342 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "posts", + "hash": "6BDD92F513B8CE90C615521D5D28BF89F5DFA5C70F7A889EBEB733CA4163B75E", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "has_create_action": true, + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "prefix": null, + "where": null, + "unique": true, + "concurrently": true, + "all_tenants?": false, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "nulls_distinct": true, + "using": null + } + ], + "custom_statements": [], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20240327211917.json b/priv/resource_snapshots/test_repo/posts/20240327211917.json new file mode 100644 index 00000000..854a935c --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240327211917.json @@ -0,0 +1,342 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "posts", + "hash": "987D3631D8B6C31C1BA0E5CCF93DDECAFA1CA45768B37AFDEEA9000A09670EF7", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "has_create_action": true, + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "unique": true, + "concurrently": true, + "all_tenants?": false, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "nulls_distinct": true, + "using": null + } + ], + "custom_statements": [], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json b/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json new file mode 100644 index 00000000..ebe6397a --- /dev/null +++ b/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json @@ -0,0 +1,67 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "description", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "profile_author_id_fkey", + "table": "authors", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "profile", + "hash": "4D5319352707FBF8EEEC4221C202ABF4F77AFE3A931697795D125CBB87F8F78C", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": "profiles", + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/records/20240327211150.json b/priv/resource_snapshots/test_repo/records/20240327211150.json new file mode 100644 index 00000000..d93eae92 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records/20240327211150.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "records", + "hash": "4947993D236ABA95A54A3AEAB01748894D857344F72470EF124993FD8E098C35", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/records/20240327211917.json b/priv/resource_snapshots/test_repo/records/20240327211917.json new file mode 100644 index 00000000..27de2e98 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records/20240327211917.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "records", + "hash": "F112A3BEAA12D1FC73C33D960C3E9A9777095B15859A297E0A2EC9B8FEE8E8A7", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json new file mode 100644 index 00000000..57e1438c --- /dev/null +++ b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"now()\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "temp_entities", + "hash": "B6BCD5D4F436B5B11C6502095CC3157DC75E3AEE723AAC8A8455500D59F1F8EE", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": "temp", + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json new file mode 100644 index 00000000..62800014 --- /dev/null +++ b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "full_name", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "inserted_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + } + ], + "table": "temp_entities", + "hash": "96A9F379FDC5364E80B0E424BFF3F43F58733AB25F6CDF5F921E36D4261E6AE1", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": "temp", + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json new file mode 100644 index 00000000..c6810e50 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json @@ -0,0 +1,95 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "org_id", + "references": { + "name": "multitenant_posts_org_id_fkey", + "table": "multitenant_orgs", + "multitenancy": { + "global": true, + "attribute": "id", + "strategy": "attribute" + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "user_id", + "references": { + "name": "multitenant_posts_user_id_fkey", + "table": "users", + "multitenancy": { + "global": true, + "attribute": "org_id", + "strategy": "attribute" + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "multitenant_posts", + "hash": "9B4FF881274BB3A9AF5DFCBDB80B33E9C213326DD3ACCFFC23FF0B45B7059461", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": false, + "attribute": null, + "strategy": "context" + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/users/20240327211150.json b/priv/resource_snapshots/test_repo/users/20240327211150.json new file mode 100644 index 00000000..7e692ca2 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20240327211150.json @@ -0,0 +1,105 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "is_active", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "users_organization_id_fkey", + "table": "orgs", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "org_id", + "references": { + "name": "users_org_id_fkey", + "table": "multitenant_orgs", + "multitenancy": { + "global": true, + "attribute": "id", + "strategy": "attribute" + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "users", + "hash": "1E8931B282681C15BD83551B4849F1C6C8C9755D45A28AEE9617527DB1DD3921", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "identities": [], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240327211150_migrate_resources19.exs b/priv/test_repo/migrations/20240327211150_migrate_resources19.exs new file mode 100644 index 00000000..787d0e26 --- /dev/null +++ b/priv/test_repo/migrations/20240327211150_migrate_resources19.exs @@ -0,0 +1,209 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources19 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(:users) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:temp_entities, prefix: "temp") do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:records) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:profile, prefix: "profiles") do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:posts) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + drop(constraint(:post_ratings, "post_ratings_resource_id_fkey")) + + alter table(:post_ratings) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + + modify( + :resource_id, + references(:posts, column: :id, name: "post_ratings_resource_id_fkey", type: :uuid) + ) + end + + alter table(:post_followers) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:orgs) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:multitenant_orgs) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:managers) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:entities) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:complex_calculations_skills) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:complex_calculations_documentations) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:complex_calculations_channels) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:complex_calculations_certifications_channel_members) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:complex_calculations_certifications) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:comments) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + drop(constraint(:comment_ratings, "comment_ratings_resource_id_fkey")) + + alter table(:comment_ratings) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + + modify( + :resource_id, + references(:comments, column: :id, name: "comment_ratings_resource_id_fkey", type: :uuid) + ) + end + + alter table(:authors) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + alter table(:accounts) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + + execute( + "ALTER TABLE comment_ratings alter CONSTRAINT comment_ratings_resource_id_fkey NOT DEFERRABLE" + ) + + execute( + "ALTER TABLE post_ratings alter CONSTRAINT post_ratings_resource_id_fkey NOT DEFERRABLE" + ) + end + + def down do + alter table(:accounts) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:authors) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + drop(constraint(:comment_ratings, "comment_ratings_resource_id_fkey")) + + alter table(:comment_ratings) do + modify( + :resource_id, + references(:comments, column: :id, name: "comment_ratings_resource_id_fkey", type: :uuid) + ) + + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:comments) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:complex_calculations_certifications) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:complex_calculations_certifications_channel_members) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:complex_calculations_channels) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:complex_calculations_documentations) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:complex_calculations_skills) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:entities) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:managers) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:multitenant_orgs) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:orgs) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:post_followers) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + drop(constraint(:post_ratings, "post_ratings_resource_id_fkey")) + + alter table(:post_ratings) do + modify( + :resource_id, + references(:posts, column: :id, name: "post_ratings_resource_id_fkey", type: :uuid) + ) + + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:posts) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:profile, prefix: "profiles") do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:records) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:temp_entities, prefix: "temp") do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + + alter table(:users) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + end +end diff --git a/priv/test_repo/migrations/20240327211917_migrate_resources20.exs b/priv/test_repo/migrations/20240327211917_migrate_resources20.exs new file mode 100644 index 00000000..030206f5 --- /dev/null +++ b/priv/test_repo/migrations/20240327211917_migrate_resources20.exs @@ -0,0 +1,99 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources20 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(:temp_entities, prefix: "temp") do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:records) do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:posts) do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:created_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:post_views) do + modify(:time, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:entities) do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:complex_calculations_documentations) do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:inserted_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:complex_calculations_channels) do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:created_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:complex_calculations_certifications_channel_members) do + modify(:updated_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + modify(:created_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + + alter table(:comments) do + modify(:created_at, :utc_datetime_usec, default: fragment("(now() AT TIME ZONE 'utc')")) + end + end + + def down do + alter table(:comments) do + modify(:created_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:complex_calculations_certifications_channel_members) do + modify(:created_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:complex_calculations_channels) do + modify(:created_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:complex_calculations_documentations) do + modify(:inserted_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:entities) do + modify(:inserted_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:post_views) do + modify(:time, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:posts) do + modify(:created_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:records) do + modify(:inserted_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + + alter table(:temp_entities, prefix: "temp") do + modify(:inserted_at, :utc_datetime_usec, default: fragment("now()")) + modify(:updated_at, :utc_datetime_usec, default: fragment("now()")) + end + end +end diff --git a/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs b/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs new file mode 100644 index 00000000..0e4da87a --- /dev/null +++ b/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources2 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(:multitenant_posts, prefix: prefix()) do + modify(:id, :uuid, default: fragment("gen_random_uuid()")) + end + end + + def down do + alter table(:multitenant_posts, prefix: prefix()) do + modify(:id, :uuid, default: fragment("uuid_generate_v4()")) + end + end +end From 3e9d92b697235e1fcc422415620125416c5a1abb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:24:18 -0400 Subject: [PATCH 0316/1215] chore: fix mix.exs --- mix.exs | 1 - mix.lock | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index d40895dd..1f88ffcc 100644 --- a/mix.exs +++ b/mix.exs @@ -156,7 +156,6 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:spark, path: "../spark", override: true}, # dev/test dependencies {:simple_sat, "~> 0.1"}, {:ash, ash_version("~> 3.0.0-rc.0")}, diff --git a/mix.lock b/mix.lock index 64f33420..ea634fcc 100644 --- a/mix.lock +++ b/mix.lock @@ -30,7 +30,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.6", "15c6725836607322e867b40fb6bdeb13f4606d48a001d2a74518559678e26895", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "6838d226e83acedb3a6169169bfc2d7afde19ef6a37fb14f0572d39db9d56c7b"}, + "spark": {:hex, :spark, "2.1.7", "72a199e905badb7b43ab6931df1d2c75bc12641fae02ff5e6374033886b07c9b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "0e4c6879f3f4a6c76fbaae51bc31efb90c48c3267c1f6159b4418df8442c8200"}, "splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From 7bd63ffcaf6ea8893c0df116fc2d5e4406a16cbc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:28:28 -0400 Subject: [PATCH 0317/1215] test: update tests with migration changes --- test/migration_generator_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 751c24f1..2446216e 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -139,7 +139,7 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration adds the id, with its default assert file_contents =~ - ~S[add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true] + ~S[add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true] # the migration adds the id, with its default assert file_contents =~ @@ -233,7 +233,7 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration adds the id, with its default assert file_contents =~ - ~S[add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true] + ~S[add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true] # the migration adds other attributes assert file_contents =~ ~S[add :title, :text] From 4ffe68732f6e30ce63dec459d76ddc38635a9b2f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:29:10 -0400 Subject: [PATCH 0318/1215] chore: release version v2.0.0-rc.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adea6ea7..a911cf3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.5.22...v2.0.0-rc.0) (2024-03-27) +### Breaking Changes: + +* change defaults for uuids to `gen_random_uuid()` + +* Use UTC for default generated timestamps (#131) + +* 3.0 (#227) + + + +### Features: + +* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Bug Fixes: + +* handle fully fleshed out aggregate fields + +### Improvements: + +* upgrade to 3.0 + +* properly show unsupported error expression + ## [v1.5.22](https://github.com/ash-project/ash_postgres/compare/v1.5.21...v1.5.22) (2024-03-20) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 1f88ffcc..b91105a8 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "1.5.22" + @version "2.0.0-rc.0" def project do [ From f71ff9bdc22babc037422fed998e0f818d32f555 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:30:30 -0400 Subject: [PATCH 0319/1215] chore: update changelog --- CHANGELOG.md | 19 +++++-------------- mix.exs | 3 ++- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a911cf3b..7d5e71ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,29 +6,20 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.0.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.5.22...v2.0.0-rc.0) (2024-03-27) -### Breaking Changes: - -* change defaults for uuids to `gen_random_uuid()` -* Use UTC for default generated timestamps (#131) - -* 3.0 (#227) +### Breaking Changes: +- change defaults for uuids to `gen_random_uuid()` +- Use UTC for default generated timestamps (#131) ### Features: -* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) - -### Bug Fixes: - -* handle fully fleshed out aggregate fields +- add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) ### Improvements: -* upgrade to 3.0 - -* properly show unsupported error expression +- show proper error when using error expresison without `ash-functions` extension ## [v1.5.22](https://github.com/ash-project/ash_postgres/compare/v1.5.21...v1.5.22) (2024-03-20) diff --git a/mix.exs b/mix.exs index b91105a8..33bdd82a 100644 --- a/mix.exs +++ b/mix.exs @@ -96,7 +96,8 @@ defmodule AshPostgres.MixProject do "documentation/topics/postgres-expressions.md", "documentation/topics/references.md", "documentation/topics/schema-based-multitenancy.md", - "documentation/dsls/DSL:-AshPostgres.DataLayer.md" + "documentation/dsls/DSL:-AshPostgres.DataLayer.md", + "CHANGELOG.md" ], groups_for_extras: [ Tutorials: ~r'documentation/tutorials', From 8a505607ab964ae81016ecbd64c7fdf0df8391d0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 17:46:51 -0400 Subject: [PATCH 0320/1215] ci: set `PG_VERSION` better for CI --- test/support/test_no_sandbox_repo.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index 81fef08f..78d3a057 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -8,7 +8,15 @@ defmodule AshPostgres.TestNoSandboxRepo do end def pg_version do - Version.parse!(System.get_env("PG_VERSION") || "16.0.0") + version = + case System.get_env("PG_VERSION") do + nil -> "16.0.0" + "14" -> "14.0.0" + "15" -> "15.0.0" + "16" -> "16.0.0" + end + + Version.parse!(version) end def installed_extensions do From 4db916f57c2e1c38dc7254af6e6ca7aaee2431a5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 18:04:42 -0400 Subject: [PATCH 0321/1215] docs: update upgrade guide --- documentation/how_to/upgrade.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/documentation/how_to/upgrade.md b/documentation/how_to/upgrade.md index 88d8675b..b3abb4a8 100644 --- a/documentation/how_to/upgrade.md +++ b/documentation/how_to/upgrade.md @@ -4,6 +4,14 @@ There are only three breaking changes in this release, one of them is very signi # AshPostgres officially supports only postgresql version 14 or higher +You will need to configure your repo version in the `pg_version/0` callback in your repo. For example: + +```elixir +def pg_version do + Version.parse!("14.0.0") +end +``` + While _most_ things will work with versions as low as 11, we are relying on features of newer postgres versions. We will not be testing against versions lower than 14, and we will not be supporting them. If you are using an older version of postgres, you will need to upgrade. If you _must_ use an older version, the only thing that you'll need to change in the short term is to handle the fact that we now use `gen_random_uuid()` as the default for generated uuids (see below), which is only available after postgres _13_. Additionally, if you are on postgres 12 or earlier, you will need to replace `ANYCOMPATIBLE` with `ANYELEMENT` in the `ash-functions` extension migration. From c41b7f3fabe40a6d727dfb948ea1397380339707 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 18:09:03 -0400 Subject: [PATCH 0322/1215] chore: update mix.lock --- mix.lock | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index ea634fcc..2a885f5f 100644 --- a/mix.lock +++ b/mix.lock @@ -8,9 +8,11 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, @@ -24,13 +26,15 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.7", "72a199e905badb7b43ab6931df1d2c75bc12641fae02ff5e6374033886b07c9b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "0e4c6879f3f4a6c76fbaae51bc31efb90c48c3267c1f6159b4418df8442c8200"}, + "spark": {:hex, :spark, "2.1.8", "406256443d5e23ec034a0520c5bee703385ce0840825194aa583c96c22c2a349", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "cc46f7b3d31efe7995d6348e1664b1e18d5193761ad5462d61059078578d5f4c"}, "splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From 3647fc42ee9fbd14feb0dc38334dd1b0666d40a6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 19:20:49 -0400 Subject: [PATCH 0323/1215] improvement: add default implementation for pg_version, and rename to `min_pg_version` --- .credo.exs | 2 +- documentation/how_to/upgrade.md | 10 +-- lib/data_layer.ex | 1 - lib/data_layer/info.ex | 6 +- lib/repo.ex | 87 +++++++++++++++++++++++- lib/verifiers/verify_postgres_version.ex | 37 ---------- lib/version_agent.ex | 0 mix.lock | 4 -- test/support/test_no_sandbox_repo.ex | 12 ---- test/support/test_repo.ex | 4 -- 10 files changed, 90 insertions(+), 73 deletions(-) delete mode 100644 lib/verifiers/verify_postgres_version.ex create mode 100644 lib/version_agent.ex diff --git a/.credo.exs b/.credo.exs index 4d3609ad..9442b952 100644 --- a/.credo.exs +++ b/.credo.exs @@ -118,7 +118,7 @@ {Credo.Check.Refactor.CondStatements, []}, {Credo.Check.Refactor.CyclomaticComplexity, false}, {Credo.Check.Refactor.FunctionArity, [max_arity: 10]}, - {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.LongQuoteBlocks, false}, {Credo.Check.Refactor.MapInto, []}, {Credo.Check.Refactor.MatchInCondition, []}, {Credo.Check.Refactor.NegatedConditionsInUnless, []}, diff --git a/documentation/how_to/upgrade.md b/documentation/how_to/upgrade.md index b3abb4a8..5ae14b1c 100644 --- a/documentation/how_to/upgrade.md +++ b/documentation/how_to/upgrade.md @@ -4,15 +4,9 @@ There are only three breaking changes in this release, one of them is very signi # AshPostgres officially supports only postgresql version 14 or higher -You will need to configure your repo version in the `pg_version/0` callback in your repo. For example: +A new callback `min_pg_version/0` has been added to the repo, but a default implementation is set up that reads the version from postgres directly, the first time it is required. It is cached until the repo is reinitialized, at which point it is looked up again. -```elixir -def pg_version do - Version.parse!("14.0.0") -end -``` - -While _most_ things will work with versions as low as 11, we are relying on features of newer postgres versions. We will not be testing against versions lower than 14, and we will not be supporting them. If you are using an older version of postgres, you will need to upgrade. +While _most_ things will work with versions as low as 9, we are relying on features of newer postgres versions and intend to do so more in the future. We will not be testing against versions lower than 14, and we will not be supporting them. If you are using an older version of postgres, you will need to upgrade. If you _must_ use an older version, the only thing that you'll need to change in the short term is to handle the fact that we now use `gen_random_uuid()` as the default for generated uuids (see below), which is only available after postgres _13_. Additionally, if you are on postgres 12 or earlier, you will need to replace `ANYCOMPATIBLE` with `ANYELEMENT` in the `ash-functions` extension migration. diff --git a/lib/data_layer.ex b/lib/data_layer.ex index fec27840..48294f5f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -385,7 +385,6 @@ defmodule AshPostgres.DataLayer do use Spark.Dsl.Extension, sections: @sections, verifiers: [ - AshPostgres.Verifiers.VerifyPostgresVersion, AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates, AshPostgres.Verifiers.ValidateReferences, AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType, diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 8126ce1e..8ed9f368 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -17,13 +17,13 @@ defmodule AshPostgres.DataLayer.Info do @doc "Checks a version requirement against the resource's repo's postgres version" def pg_version_matches?(resource, requirement) do resource - |> pg_version() + |> min_pg_version() |> Version.match?(requirement) end @doc "Gets the resource's repo's postgres version" - def pg_version(resource) do - case repo(resource, :read).pg_version() do + def min_pg_version(resource) do + case repo(resource, :read).min_pg_version() do %Version{} = version -> version string when is_binary(string) -> Version.parse!(string) end diff --git a/lib/repo.ex b/lib/repo.ex index 6437158c..2b97dcf2 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -45,7 +45,7 @@ defmodule AshPostgres.Repo do @callback installed_extensions() :: [String.t() | module()] @doc "Configure the version of postgres that is being used." - @callback pg_version() :: Version.t() + @callback min_pg_version() :: Version.t() @doc """ Use this to inform the data layer about the oldest potential postgres version it will be run on. @@ -86,6 +86,7 @@ defmodule AshPostgres.Repo do otp_app: otp_app end + @agent __MODULE__.AshPgVersion @behaviour AshPostgres.Repo defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1 @@ -98,7 +99,6 @@ defmodule AshPostgres.Repo do def create?, do: true def drop?, do: true - def transaction!(fun) do case fun.() do {:ok, value} -> value @@ -119,7 +119,19 @@ defmodule AshPostgres.Repo do """ end - def init(_, config) do + def init(type, config) do + if type == :supervisor do + try do + Agent.stop(@agent) + rescue + _ -> + :ok + catch + _, _ -> + :ok + end + end + new_config = config |> Keyword.put(:installed_extensions, installed_extensions()) @@ -193,6 +205,74 @@ defmodule AshPostgres.Repo do end end + def min_pg_version do + if version = cached_version() do + version + else + lookup_version() + end + end + + defp cached_version do + Agent.start_link( + fn -> + lookup_version() + end, + name: @agent + ) + + Agent.get(@agent, fn state -> state end) + end + + defp lookup_version do + version_string = + try do + query!("SELECT version()").rows |> Enum.at(0) |> Enum.at(0) + rescue + error -> + reraise """ + Got an error while trying to read postgres version + + Error: + + #{inspect(error)} + """, + __STACKTRACE__ + end + + try do + version_string + |> String.split(" ") + |> Enum.at(1) + |> String.split(".") + |> case do + [major] -> + "#{major}.0.0" + + [major, minor] -> + "#{major}.#{minor}.0" + + other -> + Enum.join(other, ".") + end + |> Version.parse!() + rescue + error -> + reraise( + """ + Could not parse postgres version from version string: "#{version_string}" + + You may need to define the `min_version/0` callback yourself. + + Error: + + #{inspect(error)} + """, + __STACKTRACE__ + ) + end + end + def from_ecto(other), do: other def to_ecto(nil), do: nil @@ -230,6 +310,7 @@ defmodule AshPostgres.Repo do defoverridable init: 2, on_transaction_begin: 1, installed_extensions: 0, + min_pg_version: 0, all_tenants: 0, tenant_migrations_path: 0, default_prefix: 0, diff --git a/lib/verifiers/verify_postgres_version.ex b/lib/verifiers/verify_postgres_version.ex deleted file mode 100644 index 4b36dc81..00000000 --- a/lib/verifiers/verify_postgres_version.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule AshPostgres.Verifiers.VerifyPostgresVersion do - @moduledoc false - use Spark.Dsl.Verifier - - def verify(dsl) do - read_repo = AshPostgres.DataLayer.Info.repo(dsl, :read) - mutate_repo = AshPostgres.DataLayer.Info.repo(dsl, :mutate) - - read_version = - read_repo.pg_version() |> parse!(read_repo) - - mutation_version = mutate_repo.pg_version() |> parse!(mutate_repo) - - if Version.match?(read_version, ">= 14.0.0") && Version.match?(mutation_version, ">= 14.0.0") do - :ok - else - {:error, "AshPostgres only supports postgres versions >= 14.0."} - end - end - - defp parse!(%Version{} = version, _repo) do - version - end - - defp parse!(version, repo) do - Version.parse!(version) - rescue - e -> - reraise ArgumentError, - """ - Failed to parse version in `#{inspect(repo)}.pg_version()`: #{inspect(version)} - - Error: #{Exception.message(e)} - """, - __STACKTRACE__ - end -end diff --git a/lib/version_agent.ex b/lib/version_agent.ex new file mode 100644 index 00000000..e69de29b diff --git a/mix.lock b/mix.lock index 2a885f5f..696c677d 100644 --- a/mix.lock +++ b/mix.lock @@ -8,11 +8,9 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, @@ -26,9 +24,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index 78d3a057..567d35db 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -7,18 +7,6 @@ defmodule AshPostgres.TestNoSandboxRepo do send(self(), data) end - def pg_version do - version = - case System.get_env("PG_VERSION") do - nil -> "16.0.0" - "14" -> "14.0.0" - "15" -> "15.0.0" - "16" -> "16.0.0" - end - - Version.parse!(version) - end - def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 07b29700..50d1b472 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -7,10 +7,6 @@ defmodule AshPostgres.TestRepo do send(self(), data) end - def pg_version do - Version.parse!(System.get_env("PG_VERSION") || "16.0.0") - end - def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) From 4ee813bc909cf76643f64a105f7a52f9fa522985 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 20:14:27 -0400 Subject: [PATCH 0324/1215] chore: update mix.lock --- mix.lock | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 696c677d..8cada0cf 100644 --- a/mix.lock +++ b/mix.lock @@ -8,9 +8,11 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, @@ -24,13 +26,15 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, - "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, + "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.8", "406256443d5e23ec034a0520c5bee703385ce0840825194aa583c96c22c2a349", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "cc46f7b3d31efe7995d6348e1664b1e18d5193761ad5462d61059078578d5f4c"}, + "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, "splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From cd034a0a415d782717e7e062ce20e4587e2f13a2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 20:14:51 -0400 Subject: [PATCH 0325/1215] chore: release version v2.0.0-rc.1 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5e71ed..7901ee49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.0...v2.0.0-rc.1) (2024-03-28) + + + + +### Improvements: + +* add default implementation for pg_version, and rename to `min_pg_version` + ## [v2.0.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.5.22...v2.0.0-rc.0) (2024-03-27) ### Breaking Changes: diff --git a/mix.exs b/mix.exs index 33bdd82a..0259ad84 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.0" + @version "2.0.0-rc.1" def project do [ From a787e9d8d76ca4e1887da08610a912d4fa291bf8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 20:16:30 -0400 Subject: [PATCH 0326/1215] chore: update reactor/spark --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 8cada0cf..2a885f5f 100644 --- a/mix.lock +++ b/mix.lock @@ -30,11 +30,11 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, - "reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, + "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, + "spark": {:hex, :spark, "2.1.8", "406256443d5e23ec034a0520c5bee703385ce0840825194aa583c96c22c2a349", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "cc46f7b3d31efe7995d6348e1664b1e18d5193761ad5462d61059078578d5f4c"}, "splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From f03647825cd723b6b261893e6fca5123bbaec524 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 27 Mar 2024 21:30:42 -0400 Subject: [PATCH 0327/1215] fix: ensure timestamps are present in extension migrations --- lib/migration_generator/migration_generator.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2f0cc5de..7d67f989 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -220,15 +220,15 @@ defmodule AshPostgres.MigrationGenerator do {module, migration_name} = case to_install do [{ext_name, version, _up_fn, _down_fn}] -> - {"install_#{ext_name}_v#{version}", + {"install_#{ext_name}_v#{version}_#{timestamp(true)}", "#{timestamp(true)}_install_#{ext_name}_v#{version}_extension"} ["ash-functions" = single] -> - {"install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}", + {"install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}_#{timestamp(true)}", "#{timestamp(true)}_install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}"} multiple -> - {"install_#{Enum.count(multiple)}_extensions", + {"install_#{Enum.count(multiple)}_extensions_#{timestamp(true)}", "#{timestamp(true)}_install_#{Enum.count(multiple)}_extensions"} end From a96df174bbd063fd5570620ee528f9749a9a6e6a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 28 Mar 2024 09:12:32 -0400 Subject: [PATCH 0328/1215] fix: properly handle non-filter aggregate filters --- lib/aggregate.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/aggregate.ex b/lib/aggregate.ex index 84a4cf70..2d35a6f3 100644 --- a/lib/aggregate.ex +++ b/lib/aggregate.ex @@ -1148,8 +1148,7 @@ defmodule AshPostgres.Aggregate do defp has_filter?(nil), do: false defp has_filter?(%{filter: nil}), do: false defp has_filter?(%{filter: %Ash.Filter{expression: nil}}), do: false - defp has_filter?(%{filter: %Ash.Filter{}}), do: true - defp has_filter?(_), do: false + defp has_filter?(_), do: true defp has_sort?(nil), do: false defp has_sort?(%{sort: nil}), do: false From 51ccdf112f0b64672bced5e3a3a4d8302d919517 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 29 Mar 2024 08:55:16 -0400 Subject: [PATCH 0329/1215] chore: fix mix.lock --- mix.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 0259ad84..88c38b84 100644 --- a/mix.exs +++ b/mix.exs @@ -153,13 +153,13 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:ash, ash_version("~> 3.0.0-rc.0")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies - {:simple_sat, "~> 0.1"}, - {:ash, ash_version("~> 3.0.0-rc.0")}, + {:simple_sat, "~> 0.1", only: [:dev, :test]}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, From d888a98a79a474c42714b551483ec6b211befb00 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 29 Mar 2024 08:55:50 -0400 Subject: [PATCH 0330/1215] chore: release version v2.0.0-rc.2 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7901ee49..8556742d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.1...v2.0.0-rc.2) (2024-03-29) +### Breaking Changes: + +* change defaults for uuids to `gen_random_uuid()` + +* Use UTC for default generated timestamps (#131) + +* 3.0 (#227) + + + +### Features: + +* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Bug Fixes: + +* properly handle non-filter aggregate filters + +* ensure timestamps are present in extension migrations + +* handle fully fleshed out aggregate fields + +### Improvements: + +* add default implementation for pg_version, and rename to `min_pg_version` + +* upgrade to 3.0 + +* properly show unsupported error expression + ## [v2.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.0...v2.0.0-rc.1) (2024-03-28) diff --git a/mix.exs b/mix.exs index 88c38b84..febdaf25 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.1" + @version "2.0.0-rc.2" def project do [ From 91121f6b8abb1cdfe0c668966bc394c896ede768 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 29 Mar 2024 17:02:24 -0400 Subject: [PATCH 0331/1215] chore: fix upgrade guide --- documentation/how_to/upgrade.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/how_to/upgrade.md b/documentation/how_to/upgrade.md index 5ae14b1c..3c65b03f 100644 --- a/documentation/how_to/upgrade.md +++ b/documentation/how_to/upgrade.md @@ -1,4 +1,4 @@ -# Upgrade from 2.0 to 3.0 +# Upgrade from 1.0 to 2.0 There are only three breaking changes in this release, one of them is very significant, the other two are minor. From efcc1a651b07128ad8ab468fcb78265b3c933564 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 31 Mar 2024 22:38:12 -0400 Subject: [PATCH 0332/1215] improvement: move many internals out to `AshSql` package --- lib/aggregate.ex | 1671 ------------ lib/calculation.ex | 165 -- lib/data_layer.ex | 978 +------ lib/expr.ex | 2367 ----------------- lib/join.ex | 792 ------ lib/sort.ex | 338 --- lib/{types/types.ex => sql_implementation.ex} | 122 +- mix.exs | 1 + mix.lock | 5 +- test/aggregate_test.exs | 2 +- 10 files changed, 246 insertions(+), 6195 deletions(-) delete mode 100644 lib/aggregate.ex delete mode 100644 lib/calculation.ex delete mode 100644 lib/expr.ex delete mode 100644 lib/join.ex delete mode 100644 lib/sort.ex rename lib/{types/types.ex => sql_implementation.ex} (67%) diff --git a/lib/aggregate.ex b/lib/aggregate.ex deleted file mode 100644 index 2d35a6f3..00000000 --- a/lib/aggregate.ex +++ /dev/null @@ -1,1671 +0,0 @@ -defmodule AshPostgres.Aggregate do - @moduledoc false - - require Ecto.Query - import Ecto.Query, only: [from: 2] - - @next_aggregate_names Enum.reduce(0..999, %{}, fn i, acc -> - Map.put(acc, :"aggregate_#{i}", :"aggregate_#{i + 1}") - end) - - def add_aggregates( - query, - aggregates, - resource, - select?, - source_binding, - root_data \\ nil - ) - - def add_aggregates(query, [], _, _, _, _), do: {:ok, query} - - def add_aggregates(query, aggregates, resource, select?, source_binding, root_data) do - case resource_aggregates_to_aggregates(resource, aggregates) do - {:ok, aggregates} -> - query = AshPostgres.DataLayer.default_bindings(query, resource) - - {query, aggregates} = - Enum.reduce( - aggregates, - {query, []}, - fn aggregate, {query, aggregates} -> - if is_atom(aggregate.name) do - existing_agg = query.__ash_bindings__.aggregate_defs[aggregate.name] - - if existing_agg && different_queries?(existing_agg.query, aggregate.query) do - {query, name} = use_aggregate_name(query, aggregate.name) - {query, [%{aggregate | name: name} | aggregates]} - else - {query, [aggregate | aggregates]} - end - else - {query, name} = use_aggregate_name(query, aggregate.name) - - {query, [%{aggregate | name: name} | aggregates]} - end - end - ) - - aggregates = - Enum.reject(aggregates, fn aggregate -> - Map.has_key?(query.__ash_bindings__.aggregate_defs, aggregate.name) - end) - - query = - query - |> Map.update!(:__ash_bindings__, fn bindings -> - bindings - |> Map.update!(:aggregate_defs, fn aggregate_defs -> - Map.merge(aggregate_defs, Map.new(aggregates, &{&1.name, &1})) - end) - end) - - result = - aggregates - |> Enum.reject(&already_added?(&1, query.__ash_bindings__)) - |> Enum.group_by(&{&1.relationship_path, &1.join_filters || %{}}) - |> Enum.flat_map(fn {{path, join_filters}, aggregates} -> - {can_group, cant_group} = Enum.split_with(aggregates, &can_group?(resource, &1)) - - [{{path, join_filters}, can_group}] ++ - Enum.map(cant_group, &{{path, join_filters}, [&1]}) - end) - |> Enum.filter(fn - {_, []} -> - false - - _ -> - true - end) - |> Enum.reduce_while( - {:ok, query, []}, - fn {{[first_relationship | relationship_path], join_filters}, aggregates}, - {:ok, query, dynamics} -> - first_relationship = - case Ash.Resource.Info.relationship(resource, first_relationship) do - nil -> - raise "No such relationship for #{inspect(first_relationship)} aggregates #{inspect(aggregates)}" - - first_relationship -> - first_relationship - end - - is_single? = match?([_], aggregates) - - cond do - is_single? && - optimizable_first_aggregate?(resource, Enum.at(aggregates, 0)) -> - case add_first_join_aggregate( - query, - resource, - hd(aggregates), - root_data, - first_relationship - ) do - {:ok, query, dynamic} -> - query = - if select? do - select_or_merge(query, hd(aggregates).name, dynamic) - else - query - end - - {:cont, {:ok, query, dynamics}} - - {:error, error} -> - {:halt, {:error, error}} - end - - is_single? && Enum.at(aggregates, 0).kind == :exists -> - [aggregate] = aggregates - - expr = - if is_nil(Map.get(aggregate.query, :filter)) do - true - else - Map.get(aggregate.query, :filter) - end - - {exists, acc} = - AshPostgres.Expr.dynamic_expr( - query, - %Ash.Query.Exists{path: aggregate.relationship_path, expr: expr}, - query.__ash_bindings__ - ) - - {:cont, - {:ok, AshPostgres.DataLayer.merge_expr_accumulator(query, acc), - [{aggregate.load, aggregate.name, exists} | dynamics]}} - - true -> - root_data_path = - case root_data do - {_, path} -> - path - - _ -> - [] - end - - with {:ok, subquery} <- - AshPostgres.Join.related_subquery( - first_relationship, - query, - on_subquery: fn subquery -> - current_binding = subquery.__ash_bindings__.current - - subquery = - subquery - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select(%{}) - - subquery = - if Map.get(first_relationship, :no_attributes?) do - subquery - else - if first_relationship.type == :many_to_many do - join_relationship_struct = - Ash.Resource.Info.relationship( - first_relationship.source, - first_relationship.join_relationship - ) - - {:ok, through} = - AshPostgres.Join.related_subquery( - join_relationship_struct, - query - ) - - field = first_relationship.source_attribute_on_join_resource - - subquery = - from(sub in subquery, - join: through in ^through, - as: ^current_binding, - on: - field( - through, - ^first_relationship.destination_attribute_on_join_resource - ) == - field(sub, ^first_relationship.destination_attribute), - select_merge: map(through, ^[field]), - group_by: - field( - through, - ^first_relationship.source_attribute_on_join_resource - ), - distinct: - field( - through, - ^first_relationship.source_attribute_on_join_resource - ), - where: - field( - parent_as(^source_binding), - ^first_relationship.source_attribute - ) == - field( - through, - ^first_relationship.source_attribute_on_join_resource - ) - ) - |> Map.update!(:__ash_bindings__, fn bindings -> - Map.update!(bindings, :current, &(&1 + 1)) - end) - - AshPostgres.Join.set_join_prefix( - subquery, - query, - first_relationship.destination - ) - else - field = first_relationship.destination_attribute - - if Map.get(first_relationship, :manual) do - {module, opts} = first_relationship.manual - - from(row in subquery, - group_by: field(row, ^field), - select_merge: map(row, ^[field]) - ) - - subquery = - from(row in subquery, distinct: true) - - {:ok, subquery} = - module.ash_postgres_subquery( - opts, - source_binding, - current_binding - 1, - subquery - ) - - AshPostgres.Join.set_join_prefix( - subquery, - query, - first_relationship.destination - ) - else - from(row in subquery, - group_by: field(row, ^field), - select_merge: map(row, ^[field]), - where: - field( - parent_as(^source_binding), - ^first_relationship.source_attribute - ) == - field( - as(^0), - ^first_relationship.destination_attribute - ) - ) - end - end - end - - subquery = - AshPostgres.Join.set_join_prefix( - subquery, - query, - first_relationship.destination - ) - - {:ok, subquery, _} = - apply_first_relationship_join_filters( - subquery, - query, - %AshPostgres.Expr.ExprInfo{}, - first_relationship, - join_filters - ) - - subquery = - set_in_group( - subquery, - resource - ) - - {:ok, joined} = - join_all_relationships( - subquery, - aggregates, - relationship_path, - first_relationship, - is_single?, - join_filters - ) - - {:ok, filtered} = - maybe_filter_subquery( - joined, - first_relationship, - relationship_path, - aggregates, - is_single?, - source_binding - ) - - select_all_aggregates( - aggregates, - filtered, - relationship_path, - query, - is_single?, - Ash.Resource.Info.related( - first_relationship.destination, - relationship_path - ), - first_relationship - ) - end - ), - query <- - join_subquery( - query, - subquery, - first_relationship, - relationship_path, - aggregates, - source_binding, - root_data_path - ) do - if select? do - new_dynamics = - Enum.map( - aggregates, - &{&1.load, &1.name, - select_dynamic( - resource, - query, - &1, - query.__ash_bindings__.current - 1 - )} - ) - - {:cont, {:ok, query, new_dynamics ++ dynamics}} - else - {:cont, {:ok, query, dynamics}} - end - end - end - end - ) - - case result do - {:ok, query, dynamics} -> - {:ok, add_aggregate_selects(query, dynamics)} - - {:error, error} -> - {:error, error} - end - - {:error, error} -> - {:error, error} - end - end - - defp set_in_group(%{__ash_bindings__: _} = query, _resource) do - Map.update!( - query, - :__ash_bindings__, - &Map.put(&1, :in_group?, true) - ) - end - - defp set_in_group(%Ecto.SubQuery{} = subquery, resource) do - subquery = from(row in subquery, []) - - subquery - |> AshPostgres.DataLayer.default_bindings(resource) - |> Map.update!( - :__ash_bindings__, - &Map.put(&1, :in_group?, true) - ) - end - - defp different_queries?(nil, nil), do: false - defp different_queries?(nil, _), do: true - defp different_queries?(_, nil), do: true - - defp different_queries?(query1, query2) do - query1.filter != query2.filter && query1.sort != query2.sort - end - - @doc false - def extract_shared_filters(aggregates) do - aggregates - |> Enum.reduce_while({nil, []}, fn - %{query: %{filter: filter}} = agg, {global_filters, aggs} when not is_nil(filter) -> - and_statements = - AshPostgres.DataLayer.split_and_statements(filter) - - global_filters = - if global_filters do - Enum.filter(global_filters, &(&1 in and_statements)) - else - and_statements - end - - {:cont, {global_filters, [{agg, and_statements} | aggs]}} - - _, _ -> - {:halt, {:error, aggregates}} - end) - |> case do - {:error, aggregates} -> - {:error, aggregates} - - {[], _} -> - {:error, aggregates} - - {nil, _} -> - {:error, aggregates} - - {global_filters, aggregates} -> - global_filter = and_filters(Enum.uniq(global_filters)) - - aggregates = - Enum.map(aggregates, fn {agg, and_statements} -> - applicable_and_statements = - and_statements - |> Enum.reject(&(&1 in global_filters)) - |> and_filters() - - %{agg | query: %{agg.query | filter: applicable_and_statements}} - end) - - {{:ok, global_filter}, aggregates} - end - end - - defp and_filters(filters) do - Enum.reduce(filters, nil, fn expr, acc -> - if is_nil(acc) do - expr - else - Ash.Query.BooleanExpression.new(:and, expr, acc) - end - end) - end - - defp apply_first_relationship_join_filters( - agg_root_query, - query, - acc, - first_relationship, - join_filters - ) do - case join_filters[[first_relationship]] do - nil -> - {:ok, agg_root_query, acc} - - filter -> - with {:ok, agg_root_query} <- - AshPostgres.Join.join_all_relationships(agg_root_query, filter) do - agg_root_query = - AshPostgres.Expr.set_parent_path( - agg_root_query, - query - ) - - {query, acc} = - AshPostgres.Join.maybe_apply_filter( - agg_root_query, - agg_root_query, - agg_root_query.__ash_bindings__, - filter - ) - - {:ok, query, acc} - end - end - end - - defp use_aggregate_name(query, aggregate_name) do - {%{ - query - | __ash_bindings__: %{ - query.__ash_bindings__ - | current_aggregate_name: - next_aggregate_name(query.__ash_bindings__.current_aggregate_name), - aggregate_names: - Map.put( - query.__ash_bindings__.aggregate_names, - aggregate_name, - query.__ash_bindings__.current_aggregate_name - ) - } - }, query.__ash_bindings__.current_aggregate_name} - end - - defp resource_aggregates_to_aggregates(resource, aggregates) do - Enum.reduce_while(aggregates, {:ok, []}, fn - %Ash.Query.Aggregate{} = aggregate, {:ok, aggregates} -> - {:cont, {:ok, [aggregate | aggregates]}} - - aggregate, {:ok, aggregates} -> - related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - - read_action = - aggregate.read_action || Ash.Resource.Info.primary_action!(related, :read).name - - with %{valid?: true} = aggregate_query <- Ash.Query.for_read(related, read_action), - %{valid?: true} = aggregate_query <- - Ash.Query.build(aggregate_query, filter: aggregate.filter, sort: aggregate.sort) do - Ash.Query.Aggregate.new( - resource, - aggregate.name, - aggregate.kind, - path: aggregate.relationship_path, - query: aggregate_query, - field: aggregate.field, - default: aggregate.default, - filterable?: aggregate.filterable?, - type: aggregate.type, - sortable?: aggregate.filterable?, - include_nil?: aggregate.include_nil?, - constraints: aggregate.constraints, - implementation: aggregate.implementation, - uniq?: aggregate.uniq?, - read_action: - aggregate.read_action || - Ash.Resource.Info.primary_action!( - Ash.Resource.Info.related(resource, aggregate.relationship_path), - :read - ).name, - authorize?: aggregate.authorize? - ) - else - %{valid?: false, errors: errors} -> - {:error, errors} - - {:error, error} -> - {:error, error} - end - |> case do - {:ok, aggregate} -> - aggregate = Map.put(aggregate, :load, aggregate.name) - {:cont, {:ok, [aggregate | aggregates]}} - - {:error, error} -> - {:halt, {:error, error}} - end - end) - end - - defp add_first_join_aggregate(query, resource, aggregate, root_data, first_relationship) do - {resource, path} = - case root_data do - {resource, path} -> - {resource, path} - - _ -> - {resource, []} - end - - case AshPostgres.Join.join_all_relationships( - query, - nil, - [], - [ - {:left, - AshPostgres.Join.relationship_path_to_relationships( - resource, - path ++ aggregate.relationship_path - )} - ], - [], - nil, - false - ) do - {:ok, query} -> - ref = - aggregate_field_ref( - aggregate, - Ash.Resource.Info.related(resource, path ++ aggregate.relationship_path), - path ++ aggregate.relationship_path, - query, - first_relationship - ) - - {:ok, query} = AshPostgres.Join.join_all_relationships(query, ref) - - {value, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) - - type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - - with_default = - if aggregate.default_value do - if type do - Ecto.Query.dynamic(coalesce(^value, type(^aggregate.default_value, ^type))) - else - Ecto.Query.dynamic(coalesce(^value, ^aggregate.default_value)) - end - else - value - end - - casted = - if type do - Ecto.Query.dynamic(type(^with_default, ^type)) - else - with_default - end - - {:ok, AshPostgres.DataLayer.merge_expr_accumulator(query, acc), casted} - - {:error, error} -> - {:error, error} - end - end - - defp already_added?(aggregate, bindings) do - Enum.any?(bindings.bindings, fn - {_, %{type: :aggregate, aggregates: aggregates}} -> - aggregate in aggregates - - _ -> - false - end) - end - - defp maybe_filter_subquery( - agg_query, - first_relationship, - relationship_path, - aggregates, - is_single?, - _source_binding - ) do - Enum.reduce_while(aggregates, {:ok, agg_query}, fn aggregate, {:ok, agg_query} -> - filter = - if aggregate.query.filter do - Ash.Filter.move_to_relationship_path( - aggregate.query.filter, - relationship_path - ) - |> Map.put(:resource, first_relationship.destination) - else - aggregate.query.filter - end - - related = Ash.Resource.Info.related(first_relationship.destination, relationship_path) - - field = - case aggregate.field do - field when is_atom(field) -> - Ash.Resource.Info.field(related, field) - - field -> - field - end - - agg_query = - case field do - %Ash.Query.Aggregate{} = aggregate -> - {:ok, agg_query} = - add_aggregates(agg_query, [aggregate], related, false, 0, { - first_relationship.destination, - [first_relationship.name] - }) - - agg_query - - %Ash.Resource.Aggregate{} = aggregate -> - {:ok, agg_query} = - add_aggregates(agg_query, [aggregate], related, false, 0, { - first_relationship.destination, - [first_relationship.name] - }) - - agg_query - - %Ash.Resource.Calculation{ - name: name, - calculation: {module, opts}, - type: type, - constraints: constraints - } -> - {:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, type, constraints) - expression = module.expression(opts, aggregate.context) - - expression = - Ash.Expr.fill_template( - expression, - aggregate.context[:actor], - aggregate.context, - aggregate.context - ) - - {:ok, expression} = - Ash.Filter.hydrate_refs(expression, %{ - resource: related, - public?: false - }) - - {:ok, agg_query} = - AshPostgres.DataLayer.add_calculations( - agg_query, - [{new_calc, expression}], - agg_query.__ash_bindings__.resource, - false - ) - - agg_query - - %Ash.Query.Calculation{ - module: module, - opts: opts - } = calc -> - expression = module.expression(opts, aggregate.context) - - expression = - Ash.Expr.fill_template( - expression, - aggregate.context.actor, - aggregate.context.arguments, - aggregate.context.source_context - ) - - {:ok, expression} = - Ash.Filter.hydrate_refs(expression, %{ - resource: related, - public?: false - }) - - {:ok, agg_query} = - AshPostgres.DataLayer.add_calculations( - agg_query, - [{calc, expression}], - agg_query.__ash_bindings__.resource, - false - ) - - agg_query - - _ -> - agg_query - end - - if has_filter?(aggregate.query) && is_single? do - {:cont, - AshPostgres.DataLayer.filter(agg_query, filter, agg_query.__ash_bindings__.resource)} - else - {:cont, {:ok, agg_query}} - end - end) - end - - defp join_subquery( - query, - subquery, - %{manual: {_, _}}, - _relationship_path, - aggregates, - _source_binding, - root_data_path - ) do - query = - from(row in query, - left_lateral_join: sub in ^subquery, - as: ^query.__ash_bindings__.current, - on: true - ) - - AshPostgres.DataLayer.add_binding( - query, - %{ - path: root_data_path, - type: :aggregate, - aggregates: aggregates - } - ) - end - - defp join_subquery( - query, - subquery, - %{type: :many_to_many}, - _relationship_path, - aggregates, - _source_binding, - root_data_path - ) do - query = - from(row in query, - left_lateral_join: agg in ^subquery, - as: ^query.__ash_bindings__.current, - on: true - ) - - query - |> AshPostgres.DataLayer.add_binding(%{ - path: root_data_path, - type: :aggregate, - aggregates: aggregates - }) - |> AshPostgres.DataLayer.merge_expr_accumulator(%AshPostgres.Expr.ExprInfo{}) - end - - defp join_subquery( - query, - subquery, - _first_relationship, - _relationship_path, - aggregates, - _source_binding, - root_data_path - ) do - query = - from(row in query, - left_lateral_join: agg in ^subquery, - as: ^query.__ash_bindings__.current, - on: true - ) - - AshPostgres.DataLayer.add_binding( - query, - %{ - path: root_data_path, - type: :aggregate, - aggregates: aggregates - } - ) - end - - def next_aggregate_name(i) do - @next_aggregate_names[i] || - raise Ash.Error.Framework.AssumptionFailed, - message: """ - All 1000 static names for aggregates have been used in a single query. - Congratulations, this means that you have gone so wildly beyond our imagination - of how much can fit into a single quer. Please file an issue and we will raise the limit. - """ - end - - defp select_all_aggregates( - aggregates, - joined, - relationship_path, - _query, - is_single?, - resource, - first_relationship - ) do - Enum.reduce(aggregates, joined, fn aggregate, joined -> - add_subquery_aggregate_select( - joined, - relationship_path, - aggregate, - resource, - is_single?, - first_relationship - ) - end) - end - - defp join_all_relationships( - agg_root_query, - _aggregates, - relationship_path, - first_relationship, - _is_single?, - join_filters - ) do - if Enum.empty?(relationship_path) do - {:ok, agg_root_query} - else - join_filters = - Enum.reduce(join_filters, %{}, fn {key, value}, acc -> - if List.starts_with?(key, [first_relationship.name]) do - Map.put(acc, Enum.drop(key, 1), value) - else - acc - end - end) - - AshPostgres.Join.join_all_relationships( - agg_root_query, - Map.values(join_filters), - [], - [ - {:inner, - AshPostgres.Join.relationship_path_to_relationships( - first_relationship.destination, - relationship_path - )} - ], - [], - nil, - false, - join_filters, - agg_root_query - ) - end - end - - @doc false - def can_group?(_, %{kind: :exists}), do: false - def can_group?(_, %{kind: :list}), do: false - - def can_group?(resource, aggregate) do - can_group_kind?(aggregate, resource) && !has_exists?(aggregate) && - !references_to_many_relationships?(aggregate) && - !optimizable_first_aggregate?(resource, aggregate) - end - - # We can potentially optimize this. We don't have to prevent aggregates that reference - # relationships from joining, we can - # 1. group up the ones that do join relationships by the relationships they join - # 2. potentially group them all up that join to relationships and just join to all the relationships - # but this method is predictable and easy so we're starting by just not grouping them - defp references_to_many_relationships?(aggregate) do - if aggregate.query do - aggregate.query.filter - |> Ash.Filter.relationship_paths() - |> Enum.any?(&to_many_path?(aggregate.query.resource, &1)) - else - false - end - end - - defp to_many_path?(_resource, []), do: false - - defp to_many_path?(resource, [rel | rest]) do - case Ash.Resource.Info.relationship(resource, rel) do - %{cardinality: :many} -> - true - - nil -> - raise """ - No such relationship #{inspect(rel)} for resource #{inspect(resource)} - """ - - rel -> - to_many_path?(rel.destination, rest) - end - end - - defp can_group_kind?(aggregate, resource) do - if aggregate.kind == :first do - if array_type?(resource, aggregate) || optimizable_first_aggregate?(resource, aggregate) do - false - else - true - end - else - true - end - end - - @doc false - def optimizable_first_aggregate?( - resource, - %{ - kind: :first, - relationship_path: relationship_path, - join_filters: join_filters, - field: %Ash.Query.Calculation{} = field - } - ) do - ref = - %Ash.Query.Ref{ - attribute: field, - relationship_path: relationship_path, - resource: resource - } - - with true <- join_filters == %{}, - [] <- Ash.Filter.used_aggregates(ref, :all), - [] <- Ash.Filter.relationship_paths(ref) do - true - else - _ -> - false - end - end - - def optimizable_first_aggregate?( - _resource, - %{ - kind: :first, - field: %Ash.Query.Aggregate{} - } - ) do - false - end - - def optimizable_first_aggregate?( - resource, - %{ - name: name, - kind: :first, - relationship_path: relationship_path, - join_filters: join_filters, - field: field - } = aggregate - ) do - resource - |> Ash.Resource.Info.related(relationship_path) - |> Ash.Resource.Info.field(field) - |> case do - %Ash.Resource.Aggregate{} -> - false - - %Ash.Resource.Calculation{} -> - field = aggregate_field(aggregate, resource) - - ref = - %Ash.Query.Ref{ - attribute: field, - relationship_path: relationship_path, - resource: resource - } - - with [] <- Ash.Filter.used_aggregates(ref, :all), - [] <- Ash.Filter.relationship_paths(ref) do - true - else - _ -> - false - end - - nil -> - false - - _ -> - name in AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) || - (join_filters in [nil, %{}, []] && - single_path?(resource, relationship_path)) - end - end - - def optimizable_first_aggregate?(_, _), do: false - - defp array_type?(resource, aggregate) do - related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - - case aggregate.field do - nil -> - false - - %{type: {:array, _}} -> - true - - type when is_atom(type) -> - case Ash.Resource.Info.field(related, aggregate.field).type do - {:array, _} -> - false - - _ -> - true - end - - _ -> - false - end - end - - defp has_exists?(aggregate) do - !!Ash.Filter.find(aggregate.query && aggregate.query.filter, fn - %Ash.Query.Exists{} -> true - _ -> false - end) - end - - defp add_aggregate_selects(query, dynamics) do - {in_aggregates, in_body} = - Enum.split_with(dynamics, fn {load, _name, _dynamic} -> is_nil(load) end) - - aggs = - in_body - |> Map.new(fn {load, _, dynamic} -> - {load, dynamic} - end) - - aggs = - if Enum.empty?(in_aggregates) do - aggs - else - Map.put( - aggs, - :aggregates, - Map.new(in_aggregates, fn {_, name, dynamic} -> - {name, dynamic} - end) - ) - end - - Ecto.Query.select_merge(query, ^aggs) - end - - defp select_dynamic(_resource, _query, aggregate, binding) do - type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - - field = - if type do - Ecto.Query.dynamic( - type( - field(as(^binding), ^aggregate.name), - ^type - ) - ) - else - Ecto.Query.dynamic(field(as(^binding), ^aggregate.name)) - end - - coalesced = - if is_nil(aggregate.default_value) do - field - else - if type do - Ecto.Query.dynamic( - coalesce( - ^field, - type( - ^aggregate.default_value, - ^type - ) - ) - ) - else - Ecto.Query.dynamic( - coalesce( - ^field, - ^aggregate.default_value - ) - ) - end - end - - if type do - Ecto.Query.dynamic(type(^coalesced, ^type)) - else - coalesced - end - end - - defp has_filter?(nil), do: false - defp has_filter?(%{filter: nil}), do: false - defp has_filter?(%{filter: %Ash.Filter{expression: nil}}), do: false - defp has_filter?(_), do: true - - defp has_sort?(nil), do: false - defp has_sort?(%{sort: nil}), do: false - defp has_sort?(%{sort: []}), do: false - defp has_sort?(%{sort: _}), do: true - defp has_sort?(_), do: false - - def add_subquery_aggregate_select( - query, - relationship_path, - %{kind: :first} = aggregate, - resource, - is_single?, - first_relationship - ) do - query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) - - ref = - aggregate_field_ref( - aggregate, - resource, - relationship_path, - query, - first_relationship - ) - - type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - - binding = - AshPostgres.DataLayer.get_binding( - query.__ash_bindings__.resource, - relationship_path, - query, - [:left, :inner, :root] - ) - - {field, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) - - has_sort? = has_sort?(aggregate.query) - - array_agg = - if AshPostgres.DataLayer.Info.pg_version_matches?(aggregate.resource, ">= 16.0.0") do - "any_value" - else - "array_agg" - end - - {sorted, query} = - if has_sort? || first_relationship.sort not in [nil, []] do - {sort, binding} = - if has_sort? do - {aggregate.query.sort, binding} - else - {List.wrap(first_relationship.sort), 0} - end - - {:ok, sort_expr, query} = - AshPostgres.Sort.sort( - query, - sort, - Ash.Resource.Info.related( - query.__ash_bindings__.resource, - relationship_path - ), - relationship_path, - binding, - :return - ) - - if aggregate.include_nil? do - question_marks = Enum.map(sort_expr, fn _ -> " ? " end) - - {:ok, expr} = - Ash.Query.Function.Fragment.casted_new( - ["#{array_agg}(? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))", field] ++ - sort_expr ++ [field] - ) - - {sort_expr, acc} = - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) - - query = - AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - {sort_expr, query} - else - question_marks = Enum.map(sort_expr, fn _ -> " ? " end) - - {:ok, expr} = - Ash.Query.Function.Fragment.casted_new( - ["#{array_agg}(? ORDER BY #{question_marks})", field] ++ sort_expr - ) - - {sort_expr, acc} = - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) - - query = - AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - {sort_expr, query} - end - else - case array_agg do - "array_agg" -> - {Ecto.Query.dynamic( - [row], - fragment("array_agg(?)", ^field) - ), query} - - "any_value" -> - {Ecto.Query.dynamic( - [row], - fragment("any_value(?)", ^field) - ), query} - end - end - - {query, filtered} = - filter_field(sorted, query, aggregate, relationship_path, is_single?) - - value = - if array_agg == "array_agg" do - Ecto.Query.dynamic(fragment("(?)[1]", ^filtered)) - else - filtered - end - - with_default = - if aggregate.default_value do - if type do - Ecto.Query.dynamic(coalesce(^value, type(^aggregate.default_value, ^type))) - else - Ecto.Query.dynamic(coalesce(^value, ^aggregate.default_value)) - end - else - value - end - - casted = - if type do - Ecto.Query.dynamic(type(^with_default, ^type)) - else - with_default - end - - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - select_or_merge( - query, - aggregate.name, - casted - ) - end - - def add_subquery_aggregate_select( - query, - relationship_path, - %{kind: :list} = aggregate, - resource, - is_single?, - first_relationship - ) do - query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) - type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - - binding = - AshPostgres.DataLayer.get_binding( - query.__ash_bindings__.resource, - relationship_path, - query, - [:left, :inner, :root] - ) - - ref = - aggregate_field_ref( - aggregate, - resource, - relationship_path, - query, - first_relationship - ) - - {field, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) - - has_sort? = has_sort?(aggregate.query) - - {sorted, query} = - if has_sort? || (first_relationship && first_relationship.sort not in [nil, []]) do - {sort, binding} = - if has_sort? do - {aggregate.query.sort, binding} - else - {List.wrap(first_relationship.sort), 0} - end - - {:ok, sort_expr, query} = - AshPostgres.Sort.sort( - query, - sort, - Ash.Resource.Info.related( - query.__ash_bindings__.resource, - relationship_path - ), - relationship_path, - binding, - :return - ) - - question_marks = Enum.map(sort_expr, fn _ -> " ? " end) - - distinct = - if Map.get(aggregate, :uniq?) do - "DISTINCT " - else - "" - end - - expr = - if aggregate.include_nil? do - {:ok, expr} = - Ash.Query.Function.Fragment.casted_new( - [ - "array_agg(#{distinct}? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))", - field - ] ++ - sort_expr ++ [field] - ) - - expr - else - {:ok, expr} = - Ash.Query.Function.Fragment.casted_new( - ["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr - ) - - expr - end - - {expr, acc} = - AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) - - query = - AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - {expr, query} - else - if Map.get(aggregate, :uniq?) do - {Ecto.Query.dynamic( - [row], - fragment("array_agg(DISTINCT ?)", ^field) - ), query} - else - {Ecto.Query.dynamic( - [row], - fragment("array_agg(?)", ^field) - ), query} - end - end - - {query, filtered} = - filter_field(sorted, query, aggregate, relationship_path, is_single?) - - with_default = - if aggregate.default_value do - if type do - Ecto.Query.dynamic(coalesce(^filtered, type(^aggregate.default_value, ^type))) - else - Ecto.Query.dynamic(coalesce(^filtered, ^aggregate.default_value)) - end - else - filtered - end - - cast = - if type do - Ecto.Query.dynamic(type(^with_default, ^type)) - else - with_default - end - - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - select_or_merge( - query, - aggregate.name, - cast - ) - end - - def add_subquery_aggregate_select( - query, - relationship_path, - %{kind: kind} = aggregate, - resource, - is_single?, - first_relationship - ) - when kind in [:count, :sum, :avg, :max, :min, :custom] do - query = AshPostgres.DataLayer.default_bindings(query, aggregate.resource) - - ref = - aggregate_field_ref( - aggregate, - resource, - relationship_path, - query, - first_relationship - ) - - {field, query} = - if kind == :custom do - # we won't use this if its custom so don't try to make one - {nil, query} - else - {expr, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false) - - {expr, AshPostgres.DataLayer.merge_expr_accumulator(query, acc)} - end - - type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - - binding = - AshPostgres.DataLayer.get_binding( - query.__ash_bindings__.resource, - relationship_path, - query, - [:left, :inner, :root] - ) - - field = - case kind do - :count -> - if Map.get(aggregate, :uniq?) do - Ecto.Query.dynamic([row], count(^field, :distinct)) - else - Ecto.Query.dynamic([row], count(^field)) - end - - :sum -> - Ecto.Query.dynamic([row], sum(^field)) - - :avg -> - Ecto.Query.dynamic([row], avg(^field)) - - :max -> - Ecto.Query.dynamic([row], max(^field)) - - :min -> - Ecto.Query.dynamic([row], min(^field)) - - :custom -> - {module, opts} = aggregate.implementation - - module.dynamic(opts, binding) - end - - {query, filtered} = filter_field(field, query, aggregate, relationship_path, is_single?) - - with_default = - if aggregate.default_value do - if type do - Ecto.Query.dynamic(coalesce(^filtered, type(^aggregate.default_value, ^type))) - else - Ecto.Query.dynamic(coalesce(^filtered, ^aggregate.default_value)) - end - else - filtered - end - - cast = - if type do - Ecto.Query.dynamic(type(^with_default, ^type)) - else - with_default - end - - select_or_merge(query, aggregate.name, cast) - end - - defp filter_field(field, query, _aggregate, _relationship_path, true) do - {query, field} - end - - defp filter_field(field, query, aggregate, relationship_path, _is_single?) do - if has_filter?(aggregate.query) do - filter = - Ash.Filter.move_to_relationship_path( - aggregate.query.filter, - relationship_path - ) - - used_aggregates = Ash.Filter.used_aggregates(filter, []) - - # here we bypass an inner join. - # Really, we should check if all aggs in a group - # could do the same inner join, then do an inner join - {:ok, query} = - AshPostgres.Join.join_all_relationships( - query, - filter, - [], - nil, - [], - nil, - true, - nil, - nil, - true - ) - - {:ok, query} = - AshPostgres.Aggregate.add_aggregates( - query, - used_aggregates, - query.__ash_bindings__.resource, - false, - 0 - ) - - {expr, acc} = - AshPostgres.Expr.dynamic_expr( - query, - filter, - query.__ash_bindings__, - false, - AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - ) - - {AshPostgres.DataLayer.merge_expr_accumulator(query, acc), - Ecto.Query.dynamic(filter(^field, ^expr))} - else - {query, field} - end - end - - defp select_or_merge(query, aggregate_name, casted) do - query = - if query.select do - query - else - Ecto.Query.select(query, %{}) - end - - Ecto.Query.select_merge(query, ^%{aggregate_name => casted}) - end - - def aggregate_field_ref(aggregate, resource, relationship_path, query, first_relationship) do - %Ash.Query.Ref{ - attribute: aggregate_field(aggregate, resource), - relationship_path: relationship_path, - resource: query.__ash_bindings__.resource - } - |> case do - %{attribute: %Ash.Resource.Aggregate{}} = ref -> - %{ref | relationship_path: [first_relationship.name | ref.relationship_path]} - - other -> - other - end - end - - defp single_path?(_, []), do: true - - defp single_path?(resource, [relationship | rest]) do - relationship = Ash.Resource.Info.relationship(resource, relationship) - - !Map.get(relationship, :from_many?) && - (relationship.type == :belongs_to || - has_one_with_identity?(relationship)) && - single_path?(relationship.destination, rest) - end - - defp has_one_with_identity?(%{type: :has_one, from_many?: false} = relationship) do - Ash.Resource.Info.primary_key(relationship.destination) == [ - relationship.destination_attribute - ] || - relationship.destination - |> Ash.Resource.Info.identities() - |> Enum.any?(fn %{keys: keys} -> - keys == [relationship.destination_attribute] - end) - end - - defp has_one_with_identity?(_), do: false - - @doc false - def aggregate_field(aggregate, resource) do - if is_atom(aggregate.field) do - case Ash.Resource.Info.field( - resource, - aggregate.field || List.first(Ash.Resource.Info.primary_key(resource)) - ) do - %Ash.Resource.Calculation{calculation: {module, opts}} = calculation -> - calc_type = - AshPostgres.Types.parameterized_type( - calculation.type, - Map.get(calculation, :constraints, []) - ) - - AshPostgres.Expr.validate_type!(resource, calc_type, "#{inspect(calculation.name)}") - - {:ok, query_calc} = - Ash.Query.Calculation.new( - calculation.name, - module, - opts, - calculation.type, - Map.get(aggregate, :context, %{}) - ) - - query_calc - - other -> - other - end - else - aggregate.field - end - end -end diff --git a/lib/calculation.ex b/lib/calculation.ex deleted file mode 100644 index 1e030287..00000000 --- a/lib/calculation.ex +++ /dev/null @@ -1,165 +0,0 @@ -defmodule AshPostgres.Calculation do - @moduledoc false - - require Ecto.Query - - @next_calculation_names Enum.reduce(0..999, %{}, fn i, acc -> - Map.put(acc, :"calculation_#{i}", :"calculation_#{i + 1}") - end) - - def add_calculations(query, [], _, _, _select?), do: {:ok, query} - - def add_calculations(query, calculations, resource, source_binding, select?) do - query = AshPostgres.DataLayer.default_bindings(query, resource) - - {:ok, query} = - AshPostgres.Join.join_all_relationships( - query, - %Ash.Filter{ - resource: resource, - expression: Enum.map(calculations, &elem(&1, 1)) - }, - left_only?: true - ) - - aggregates = - calculations - |> Enum.flat_map(fn {calculation, expression} -> - expression - |> Ash.Filter.used_aggregates([]) - |> Enum.map(&Map.put(&1, :context, calculation.context)) - end) - |> Enum.uniq() - - {query, calculations} = - Enum.reduce( - calculations, - {query, []}, - fn {calculation, expression}, {query, calculations} -> - if is_atom(calculation.name) do - {query, [{calculation, expression} | calculations]} - else - {query, name} = use_calculation_name(query, calculation.name) - - {query, [{%{calculation | name: name}, expression} | calculations]} - end - end - ) - - case AshPostgres.Aggregate.add_aggregates( - query, - aggregates, - query.__ash_bindings__.resource, - false, - source_binding - ) do - {:ok, query} -> - if select? do - query = - if query.select do - query - else - Ecto.Query.select_merge(query, %{}) - end - - {dynamics, query} = - Enum.reduce(calculations, {[], query}, fn {calculation, expression}, {list, query} -> - type = - AshPostgres.Types.parameterized_type( - calculation.type, - Map.get(calculation, :constraints, []) - ) - - expression = - Ash.Actions.Read.add_calc_context_to_filter( - expression, - calculation.context.actor, - calculation.context.authorize?, - calculation.context.tenant, - calculation.context.tracer, - nil - ) - - {expr, acc} = - AshPostgres.Expr.dynamic_expr( - query, - expression, - query.__ash_bindings__, - false, - type - ) - - expr = - if type do - Ecto.Query.dynamic(type(^expr, ^type)) - else - expr - end - - {[{calculation.load, calculation.name, expr} | list], - AshPostgres.DataLayer.merge_expr_accumulator(query, acc)} - end) - - {:ok, add_calculation_selects(query, dynamics)} - else - {:ok, query} - end - - {:error, error} -> - {:error, error} - end - end - - def next_calculation_name(i) do - @next_calculation_names[i] || - raise Ash.Error.Framework.AssumptionFailed, - message: """ - All 1000 static names for calculations have been used in a single query. - Congratulations, this means that you have gone so wildly beyond our imagination - of how much can fit into a single quer. Please file an issue and we will raise the limit. - """ - end - - defp use_calculation_name(query, aggregate_name) do - {%{ - query - | __ash_bindings__: %{ - query.__ash_bindings__ - | current_calculation_name: - next_calculation_name(query.__ash_bindings__.current_calculation_name), - calculation_names: - Map.put( - query.__ash_bindings__.calculation_names, - aggregate_name, - query.__ash_bindings__.current_calculation_name - ) - } - }, query.__ash_bindings__.current_calculation_name} - end - - defp add_calculation_selects(query, dynamics) do - {in_calculations, in_body} = - Enum.split_with(dynamics, fn {load, _name, _dynamic} -> is_nil(load) end) - - calcs = - in_body - |> Map.new(fn {load, _, dynamic} -> - {load, dynamic} - end) - - calcs = - if Enum.empty?(in_calculations) do - calcs - else - Map.put( - calcs, - :calculations, - Map.new(in_calculations, fn {_, name, dynamic} -> - {name, dynamic} - end) - ) - end - - Ecto.Query.select_merge(query, ^calcs) - end -end diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 48294f5f..e7165cd9 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -371,9 +371,6 @@ defmodule AshPostgres.DataLayer do ] } - alias Ash.Filter - alias Ash.Query.{BooleanExpression, Not, Ref} - @behaviour Ash.DataLayer @sections [@postgres] @@ -549,79 +546,7 @@ defmodule AshPostgres.DataLayer do @impl true def set_context(resource, data_layer_query, context) do - start_bindings = context[:data_layer][:start_bindings_at] || 0 - data_layer_query = from(row in data_layer_query, as: ^start_bindings) - - data_layer_query = - if context[:data_layer][:table] do - %{ - data_layer_query - | from: %{data_layer_query.from | source: {context[:data_layer][:table], resource}} - } - else - data_layer_query - end - - data_layer_query = - if context[:data_layer][:schema] do - Ecto.Query.put_query_prefix(data_layer_query, to_string(context[:data_layer][:schema])) - else - data_layer_query - end - - data_layer_query = - data_layer_query - |> default_bindings(resource, context) - |> add_parent_bindings(context) - - case context[:data_layer][:lateral_join_source] do - {_, [{%{resource: resource}, _, _, _} | rest]} -> - parent = - resource - |> resource_to_query(nil) - |> default_bindings(resource, context) - - parent = - case rest do - [{resource, _, _, %{name: join_relationship_name}} | _] -> - binding_data = %{type: :inner, path: [join_relationship_name], source: resource} - add_binding(parent, binding_data) - - _ -> - parent - end - - query_with_ash_bindings = - data_layer_query - |> add_parent_bindings(%{data_layer: %{parent_bindings: parent.__ash_bindings__}}) - |> Map.update!(:__ash_bindings__, &Map.put(&1, :lateral_join?, true)) - - {:ok, query_with_ash_bindings} - - _ -> - ash_bindings = - data_layer_query.__ash_bindings__ - |> Map.put(:lateral_join?, false) - - {:ok, %{data_layer_query | __ash_bindings__: ash_bindings}} - end - end - - defp add_parent_bindings(data_layer_query, %{data_layer: %{parent_bindings: parent_bindings}}) - when not is_nil(parent_bindings) do - new_bindings = - data_layer_query.__ash_bindings__ - |> Map.put(:parent_bindings, Map.put(parent_bindings, :parent?, true)) - |> Map.put(:parent_resources, [ - parent_bindings.resource | parent_bindings[:parent_resources] || [] - ]) - |> Map.put(:lateral_join?, true) - - %{data_layer_query | __ash_bindings__: new_bindings} - end - - defp add_parent_bindings(data_layer_query, _context) do - data_layer_query + AshSql.Query.set_context(resource, data_layer_query, context) end @impl true @@ -636,75 +561,26 @@ defmodule AshPostgres.DataLayer do end @impl true - def return_query(%{__ash_bindings__: %{lateral_join?: true}} = query, resource) do - query = default_bindings(query, resource) - - if query.__ash_bindings__[:sort_applied?] do - {:ok, query} - else - apply_sort(query, query.__ash_bindings__[:sort], query.__ash_bindings__.resource) - end - end - def return_query(query, resource) do - query = default_bindings(query, resource) - - with_sort_applied = - if query.__ash_bindings__[:sort_applied?] do - {:ok, query} - else - apply_sort(query, query.__ash_bindings__[:sort], resource) - end - - case with_sort_applied do - {:error, error} -> - {:error, error} - - {:ok, query} -> - if query.__ash_bindings__[:__order__?] && query.windows[:order] do - if query.distinct do - query_with_order = - from(row in query, select_merge: %{__order__: over(row_number(), :order)}) - - query_without_limit_and_offset = - query_with_order - |> Ecto.Query.exclude(:limit) - |> Ecto.Query.exclude(:offset) - - {:ok, - from(row in subquery(query_without_limit_and_offset), - select: row, - order_by: row.__order__ - ) - |> Map.put(:limit, query.limit) - |> Map.put(:offset, query.offset)} - else - order_by = %{query.windows[:order] | expr: query.windows[:order].expr[:order_by]} - - {:ok, - %{ - query - | windows: Keyword.delete(query.windows, :order), - order_bys: [order_by] - }} - end - else - {:ok, %{query | windows: Keyword.delete(query.windows, :order)}} - end - end + AshSql.Query.return_query(query, resource) end @impl true def run_query(query, resource) do - query = default_bindings(query, resource) + query = AshSql.Bindings.default_bindings(query, AshPostgres.SqlImplementation, resource) if AshPostgres.DataLayer.Info.polymorphic?(resource) && no_table?(query) do raise_table_error!(resource, :read) else - repo = dynamic_repo(resource, query) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, query) with_savepoint(repo, query, fn -> - {:ok, repo.all(query, repo_opts(repo, nil, nil, resource)) |> remap_mapped_fields(query)} + {:ok, + repo.all( + query, + AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, resource) + ) + |> remap_mapped_fields(query)} end) end rescue @@ -715,34 +591,6 @@ defmodule AshPostgres.DataLayer do defp no_table?(%{from: %{source: {"", _}}}), do: true defp no_table?(_), do: false - defp repo_opts(_repo, timeout, nil, resource) do - if schema = AshPostgres.DataLayer.Info.schema(resource) do - [prefix: schema] - else - [] - end - |> add_timeout(timeout) - end - - defp repo_opts(_repo, timeout, tenant, resource) do - if Ash.Resource.Info.multitenancy_strategy(resource) == :context do - [prefix: Ash.ToTenant.to_tenant(resource, tenant)] - else - if schema = AshPostgres.DataLayer.Info.schema(resource) do - [prefix: schema] - else - [] - end - end - |> add_timeout(timeout) - end - - defp add_timeout(opts, timeout) when not is_nil(timeout) do - Keyword.put(opts, :timeout, timeout) - end - - defp add_timeout(opts, _), do: opts - @impl true def functions(resource) do config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() @@ -774,194 +622,12 @@ defmodule AshPostgres.DataLayer do @impl true def run_aggregate_query(original_query, aggregates, resource) do - original_query = default_bindings(original_query, resource) - - {can_group, cant_group} = - aggregates - |> Enum.split_with(&AshPostgres.Aggregate.can_group?(resource, &1)) - |> case do - {[one], cant_group} -> {[], [one | cant_group]} - {can_group, cant_group} -> {can_group, cant_group} - end - - {global_filter, can_group} = - AshPostgres.Aggregate.extract_shared_filters(can_group) - - query = - case global_filter do - {:ok, global_filter} -> - filter(original_query, global_filter, resource) - - :error -> - {:ok, original_query} - end - - case query do - {:error, error} -> - {:error, error} - - {:ok, query} -> - query = - if query.distinct || query.limit do - query = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - - from(row in subquery(query), as: ^0, select: %{}) - else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) - end - - query = - Enum.reduce( - can_group, - query, - fn agg, query -> - first_relationship = - Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) - - AshPostgres.Aggregate.add_subquery_aggregate_select( - query, - agg.relationship_path |> Enum.drop(1), - agg, - resource, - false, - first_relationship - ) - end - ) - - result = - case can_group do - [] -> - %{} - - _ -> - repo = dynamic_repo(resource, query) - repo.one(query, repo_opts(repo, nil, nil, resource)) - end - - {:ok, add_single_aggs(result, resource, original_query, cant_group)} - end - end - - defp add_single_aggs(result, resource, query, cant_group) do - Enum.reduce(cant_group, result, fn - %{kind: :exists} = agg, result -> - {:ok, filtered} = - case agg do - %{query: %{filter: filter}} when not is_nil(filter) -> - filter(query, filter, resource) - - _ -> - {:ok, query} - end - - filtered = - if filtered.distinct || filtered.limit do - filtered = - filtered - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - - from(row in subquery(filtered), as: ^0, select: %{}) - else - filtered - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) - end - - repo = dynamic_repo(resource, filtered) - - Map.put( - result || %{}, - agg.name, - repo.exists?(filtered, repo_opts(repo, nil, nil, resource)) - ) - - agg, result -> - {:ok, filtered} = - case agg do - %{query: %{filter: filter}} when not is_nil(filter) -> - filter(query, filter, resource) - - _ -> - {:ok, query} - end - - filtered = - if filtered.distinct do - in_query = filtered |> Ecto.Query.exclude(:distinct) |> Ecto.Query.exclude(:select) - - dynamic = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row], - ^dynamic and field(parent_as(^0), ^key) == field(row, ^key) - ) - else - Ecto.Query.dynamic( - [row], - field(parent_as(^0), ^key) == field(row, ^key) - ) - end - end) - - in_query = - from(row in in_query, where: ^dynamic) - - from(row in query.from.source, as: ^0, where: exists(in_query)) - else - filtered - end - - filtered = - if filtered.limit do - filtered = - filtered - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - - from(row in subquery(filtered), as: ^0, select: %{}) - else - filtered - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> Map.put(:windows, []) - |> Ecto.Query.select(%{}) - end - - first_relationship = - Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0)) - - query = - AshPostgres.Aggregate.add_subquery_aggregate_select( - filtered, - agg.relationship_path |> Enum.drop(1), - %{agg | query: %{agg.query | filter: nil}}, - resource, - true, - first_relationship - ) - - repo = dynamic_repo(resource, query) - - Map.merge( - result || %{}, - repo.one(query, repo_opts(repo, nil, nil, resource)) - ) - end) + AshSql.AggregateQuery.run_aggregate_query( + original_query, + aggregates, + resource, + AshPostgres.SqlImplementation + ) end @impl true @@ -979,7 +645,7 @@ defmodule AshPostgres.DataLayer do ) do {can_group, cant_group} = aggregates - |> Enum.split_with(&AshPostgres.Aggregate.can_group?(destination_resource, &1)) + |> Enum.split_with(&AshSql.Aggregate.can_group?(destination_resource, &1, query)) |> case do {[one], cant_group} -> {[], [one | cant_group]} {can_group, cant_group} -> {can_group, cant_group} @@ -998,10 +664,16 @@ defmodule AshPostgres.DataLayer do |> Map.get(:resource) subquery = from(row in subquery(lateral_join_query), as: ^0, select: %{}) - subquery = default_bindings(subquery, source_resource) + + subquery = + AshSql.Bindings.default_bindings( + subquery, + AshPostgres.SqlImplementation, + source_resource + ) {global_filter, can_group} = - AshPostgres.Aggregate.extract_shared_filters(can_group) + AshSql.Aggregate.extract_shared_filters(can_group) original_subquery = subquery @@ -1036,7 +708,7 @@ defmodule AshPostgres.DataLayer do agg.relationship_path |> Enum.at(0) ) - AshPostgres.Aggregate.add_subquery_aggregate_select( + AshSql.Aggregate.add_subquery_aggregate_select( subquery, agg.relationship_path |> Enum.drop(1), agg, @@ -1053,15 +725,29 @@ defmodule AshPostgres.DataLayer do %{} _ -> - repo = dynamic_repo(source_resource, query) + repo = + AshSql.dynamic_repo(source_resource, AshPostgres.SqlImplementation, query) repo.one( query, - repo_opts(repo, nil, nil, source_resource) + AshSql.repo_opts( + repo, + AshPostgres.SqlImplementation, + nil, + nil, + source_resource + ) ) end - {:ok, add_single_aggs(result, source_resource, original_subquery, cant_group)} + {:ok, + AshSql.AggregateQuery.add_single_aggs( + result, + source_resource, + original_subquery, + cant_group, + AshPostgres.SqlImplementation + )} end {:error, error} -> @@ -1088,12 +774,13 @@ defmodule AshPostgres.DataLayer do |> elem(0) |> Map.get(:resource) - repo = dynamic_repo(source_resource, lateral_join_query) + repo = + AshSql.dynamic_repo(source_resource, AshPostgres.SqlImplementation, lateral_join_query) results = repo.all( lateral_join_query, - repo_opts(repo, nil, nil, source_resource) + AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, source_resource) ) |> remap_mapped_fields(query) @@ -1409,7 +1096,7 @@ defmodule AshPostgres.DataLayer do @impl true def resource_to_query(resource, _) do - from(row in {AshPostgres.DataLayer.Info.table(resource) || "", resource}, []) + AshSql.Query.resource_to_query(resource, AshPostgres.SqlImplementation) end @impl true @@ -1431,10 +1118,18 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> try do - repo = dynamic_repo(resource, changeset) - repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + + repo_opts = + AshSql.repo_opts( + repo, + AshPostgres.SqlImplementation, + changeset.timeout, + changeset.tenant, + changeset.resource + ) - case query_with_atomics( + case AshSql.Atomics.query_with_atomics( resource, query, changeset.filter, @@ -1494,7 +1189,7 @@ defmodule AshPostgres.DataLayer do Ash.Filter.used_aggregates(expr, []) with {:ok, query} <- - AshPostgres.Join.join_all_relationships( + AshSql.Join.join_all_relationships( query, %Ash.Filter{ resource: resource, @@ -1503,7 +1198,7 @@ defmodule AshPostgres.DataLayer do left_only?: true ), {:ok, query} <- - AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do + AshSql.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do {:cont, {:ok, query}} else {:error, error} -> @@ -1516,14 +1211,14 @@ defmodule AshPostgres.DataLayer do nil -> {:ok, query - |> default_bindings(resource, context) + |> AshSql.Bindings.default_bindings(resource, AshSql.Implementation, context) |> Ecto.Query.exclude(:select) |> Ecto.Query.exclude(:order_by)} %{qual: :inner} -> {:ok, query - |> default_bindings(resource, context) + |> AshSql.Bindings.default_bindings(resource, AshSql.Implementation, context) |> Ecto.Query.exclude(:select) |> Ecto.Query.exclude(:order_by)} @@ -1592,7 +1287,11 @@ defmodule AshPostgres.DataLayer do try do query = query - |> default_bindings(resource, changeset.context) + |> AshSql.Bindings.default_bindings( + resource, + AshPostgres.SqlImplementation, + changeset.context + ) query = if options[:return_records?] do @@ -1604,15 +1303,26 @@ defmodule AshPostgres.DataLayer do end |> Ecto.Query.exclude(:order_by) - repo = dynamic_repo(resource, changeset) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) + repo_opts = + AshSql.repo_opts( + repo, + AshPostgres.SqlImplementation, + changeset.timeout, + changeset.tenant, + changeset.resource + ) query = if Enum.any?(query.joins, &(&1.qual != :inner)) do root_query = from(row in query.from.source, []) - |> default_bindings(resource, changeset.context) + |> AshSql.Bindings.default_bindings( + resource, + AshPostgres.SqlImplementation, + changeset.context + ) |> Ecto.Query.exclude(:select) |> Ecto.Query.exclude(:order_by) @@ -1670,9 +1380,9 @@ defmodule AshPostgres.DataLayer do def bulk_create(resource, stream, options) do changesets = Enum.to_list(stream) - repo = dynamic_repo(resource, Enum.at(changesets, 0)) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, Enum.at(changesets, 0)) - opts = repo_opts(repo, nil, options[:tenant], resource) + opts = AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, options[:tenant], resource) opts = if options.return_records? do @@ -1694,13 +1404,13 @@ defmodule AshPostgres.DataLayer do query = query - |> default_bindings(resource) + |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation) upsert_set = upsert_set(resource, changesets, options) on_conflict = - case query_with_atomics( + case AshSql.Atomics.query_with_atomics( resource, query, filter, @@ -1786,7 +1496,7 @@ defmodule AshPostgres.DataLayer do repo, %{ __ash_bindings__: %{ - expression_accumulator: %AshPostgres.Expr.ExprInfo{has_error?: true} + expression_accumulator: %AshSql.Expr.ExprInfo{has_error?: true} } }, fun @@ -2602,7 +2312,11 @@ defmodule AshPostgres.DataLayer do query = from(row in source, as: ^0) - |> default_bindings(resource, changeset.context) + |> AshSql.Bindings.default_bindings( + resource, + AshPostgres.SqlImplementation, + changeset.context + ) |> pkey_filter(changeset.data) select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) @@ -2615,7 +2329,7 @@ defmodule AshPostgres.DataLayer do query = Ecto.Query.select(query, ^select) try do - case query_with_atomics( + case AshSql.Atomics.query_with_atomics( resource, query, changeset.filter, @@ -2627,8 +2341,16 @@ defmodule AshPostgres.DataLayer do {:ok, changeset.data} {:ok, query} -> - repo = dynamic_repo(resource, changeset) - repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + + repo_opts = + AshSql.repo_opts( + repo, + AshPostgres.SqlImplementation, + changeset.timeout, + changeset.tenant, + changeset.resource + ) repo_opts = Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) @@ -2680,125 +2402,35 @@ defmodule AshPostgres.DataLayer do Ecto.Query.where(query, ^pkey) end - defp query_with_atomics( - resource, - query, - filter, - atomics, - updating_one_changes, - existing_set - ) do - query = - if is_nil(filter) do - query - else - filter(query, filter, resource) - end - - atomics_result = - Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> - attribute = Ash.Resource.Info.attribute(resource, field) - - type = - AshPostgres.Types.parameterized_type( - attribute.type, - attribute.constraints - ) - - case AshPostgres.Expr.dynamic_expr( - query, - expr, - Map.put(query.__ash_bindings__, :location, :update), - false, - type - ) do - {dynamic, acc} -> - {:cont, {:ok, merge_expr_accumulator(query, acc), Keyword.put(set, field, dynamic)}} - - other -> - {:halt, other} - end - end) - - case atomics_result do - {:ok, query, dynamics} -> - {params, set, count} = - updating_one_changes - |> Map.to_list() - |> Enum.reduce({[], [], 0}, fn {key, value}, {params, set, count} -> - {[{value, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1} - end) - - {params, set, _, query} = - Enum.reduce( - dynamics ++ existing_set, - {params, set, count, query}, - fn {key, value}, {params, set, count, query} -> - case AshPostgres.Expr.dynamic_expr(query, value, query.__ash_bindings__) do - {%Ecto.Query.DynamicExpr{} = dynamic, acc} -> - result = - Ecto.Query.Builder.Dynamic.partially_expand( - :select, - query, - dynamic, - params, - count - ) - - expr = elem(result, 0) - new_params = elem(result, 1) - - new_count = - result |> Tuple.to_list() |> List.last() - - {new_params, [{key, expr} | set], new_count, merge_expr_accumulator(query, acc)} - - {other, acc} -> - {[{other, {0, key}} | params], [{key, {:^, [], [count]}} | set], count + 1, - merge_expr_accumulator(query, acc)} - end - end - ) - - case set do - [] -> - :empty - - set -> - {:ok, - Map.put(query, :updates, [ - %Ecto.Query.QueryExpr{ - # why do I have to reverse the `set`??? - # it breaks if I don't - expr: [set: Enum.reverse(set)], - params: Enum.reverse(params) - } - ])} - end - - {:error, error} -> - {:error, error} - end - end - @impl true + def destroy(resource, %{data: record} = changeset) do ecto_changeset = ecto_changeset(record, changeset, :delete) try do - repo = dynamic_repo(resource, changeset) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) source = resolve_source(resource, changeset) from(row in source, as: ^0) - |> default_bindings(resource, changeset.context) + |> AshSql.Bindings.default_bindings( + resource, + AshPostgres.SqlImplementation, + changeset.context + ) |> filter(changeset.filter, resource) |> case do {:ok, query} -> query |> pkey_filter(record) |> repo.delete_all( - repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource) + AshSql.repo_opts( + repo, + AshPostgres.SqlImplementation, + changeset.timeout, + changeset.tenant, + changeset.resource + ) ) :ok @@ -2858,13 +2490,8 @@ defmodule AshPostgres.DataLayer do end @impl true - def select(query, select, resource) do - query = default_bindings(query, resource) - - {:ok, - from(row in query, - select: struct(row, ^Enum.uniq(select)) - )} + def select(query, select, _resource) do + {:ok, from(row in query, select: struct(row, ^Enum.uniq(select)))} end @impl true @@ -2880,237 +2507,23 @@ defmodule AshPostgres.DataLayer do # to limit to only distinct rows. This may not perform that well, so we may need # to come up with alternatives here. @impl true - def distinct(query, empty, resource) when empty in [nil, []] do - query |> apply_sort(query.__ash_bindings__[:sort], resource) - end - - def distinct(query, distinct_on, resource) do - case get_distinct_statement(query, distinct_on) do - {:ok, {distinct_statement, query}} -> - %{query | distinct: distinct_statement} - |> apply_sort(query.__ash_bindings__[:sort], resource) - - {:error, {distinct_statement, query}} -> - query - |> Ecto.Query.exclude(:order_by) - |> default_bindings(resource) - |> Map.put(:distinct, distinct_statement) - |> apply_sort( - query.__ash_bindings__[:distinct_sort] || query.__ash_bindings__[:sort], - resource, - :direct - ) - |> case do - {:ok, distinct_query} -> - on = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row, distinct], - ^dynamic and field(row, ^key) == field(distinct, ^key) - ) - else - Ecto.Query.dynamic([row, distinct], field(row, ^key) == field(distinct, ^key)) - end - end) - - joined_query_source = - Enum.reduce( - [ - :join, - :order_by, - :group_by, - :having, - :distinct, - :select, - :combinations, - :with_ctes, - :limit, - :offset, - :lock, - :preload, - :update, - :where - ], - query, - &Ecto.Query.exclude(&2, &1) - ) - - joined_query = - from(row in joined_query_source, - join: distinct in subquery(distinct_query), - on: ^on - ) - - from([row, distinct] in joined_query, - select: distinct - ) - |> default_bindings(resource) - |> apply_sort(query.__ash_bindings__[:sort], resource) - |> case do - {:ok, joined_query} -> - {:ok, - Map.update!( - joined_query, - :__ash_bindings__, - &Map.put(&1, :__order__?, query.__ash_bindings__[:__order__?] || false) - )} - - {:error, error} -> - {:error, error} - end - - {:error, error} -> - {:error, error} - end - end - end - - defp apply_sort(query, sort, resource, type \\ :window) - - defp apply_sort(query, sort, _resource, _) when sort in [nil, []] do - {:ok, query |> set_sort_applied()} - end - - defp apply_sort(query, sort, resource, type) do - AshPostgres.Sort.sort(query, sort, resource, [], 0, type) - end - - defp set_sort_applied(query) do - Map.update!(query, :__ash_bindings__, &Map.put(&1, :sort_applied?, true)) - end - - defp get_distinct_statement(query, distinct_on) do - has_distinct_sort? = match?(%{__ash_bindings__: %{distinct_sort: _}}, query) - - if has_distinct_sort? do - {:error, default_distinct_statement(query, distinct_on)} - else - sort = query.__ash_bindings__[:sort] || [] - - distinct = - query.distinct || - %Ecto.Query.QueryExpr{ - expr: [], - params: [] - } - - if sort == [] do - {:ok, default_distinct_statement(query, distinct_on)} - else - distinct_on - |> Enum.reduce_while({sort, [], [], Enum.count(distinct.params), query}, fn - _, {[], _distinct_statement, _, _count, _query} -> - {:halt, :error} - - distinct_on, {[order_by | rest_order_by], distinct_statement, params, count, query} -> - case order_by do - {distinct_on, order} = ^distinct_on -> - {distinct_expr, params, count, query} = - distinct_on_expr(query, distinct_on, params, count) - - {:cont, - {rest_order_by, [{order, distinct_expr} | distinct_statement], params, count, - query}} - - _ -> - {:halt, :error} - end - end) - |> case do - :error -> - {:error, default_distinct_statement(query, distinct_on)} - - {_, result, params, _, query} -> - {:ok, - {%{ - distinct - | expr: distinct.expr ++ Enum.reverse(result), - params: distinct.params ++ Enum.reverse(params) - }, query}} - end - end - end - end - - defp default_distinct_statement(query, distinct_on) do - distinct = - query.distinct || - %Ecto.Query.QueryExpr{ - expr: [] - } - - {expr, params, _, query} = - Enum.reduce(distinct_on, {[], [], Enum.count(distinct.params), query}, fn - {distinct_on_field, order}, {expr, params, count, query} -> - {distinct_expr, params, count, query} = - distinct_on_expr(query, distinct_on_field, params, count) - - {[{order, distinct_expr} | expr], params, count, query} - - distinct_on_field, {expr, params, count, query} -> - {distinct_expr, params, count, query} = - distinct_on_expr(query, distinct_on_field, params, count) - - {[{:asc, distinct_expr} | expr], params, count, query} - end) - - {%{ - distinct - | expr: distinct.expr ++ Enum.reverse(expr), - params: distinct.params ++ Enum.reverse(params) - }, query} - end - - defp distinct_on_expr(query, field, params, count) do - resource = query.__ash_bindings__.resource - - ref = - case field do - %Ash.Query.Calculation{} = calc -> - %Ref{attribute: calc, relationship_path: [], resource: resource} - - field -> - %Ref{ - attribute: Ash.Resource.Info.field(resource, field), - relationship_path: [], - resource: resource - } - end - - {dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__) - - result = - Ecto.Query.Builder.Dynamic.partially_expand( - :distinct, - query, - dynamic, - params, - count - ) - - expr = elem(result, 0) - new_params = elem(result, 1) - new_count = result |> Tuple.to_list() |> List.last() - - {expr, new_params, new_count, merge_expr_accumulator(query, acc)} + def distinct(query, distinct, resource) do + AshSql.Distinct.distinct(query, distinct, resource) end @impl true def filter(query, filter, resource, opts \\ []) do - query = default_bindings(query, resource) - used_aggregates = Ash.Filter.used_aggregates(filter, []) query - |> AshPostgres.Join.join_all_relationships(filter, opts) + |> AshSql.Join.join_all_relationships(filter, opts) |> case do {:ok, query} -> query - |> AshPostgres.Aggregate.add_aggregates(used_aggregates, resource, false, 0) + |> AshSql.Aggregate.add_aggregates(used_aggregates, resource, false, 0) |> case do {:ok, query} -> - {:ok, add_filter_expression(query, filter)} + {:ok, AshSql.Filter.add_filter_expression(query, filter)} {:error, error} -> {:error, error} @@ -3121,132 +2534,14 @@ defmodule AshPostgres.DataLayer do end end - @doc false - def default_bindings(query, resource, context \\ %{}) - def default_bindings(%{__ash_bindings__: _} = query, _resource, _context), do: query - - def default_bindings(query, resource, context) do - start_bindings = context[:data_layer][:start_bindings_at] || 0 - - Map.put_new(query, :__ash_bindings__, %{ - resource: resource, - current: Enum.count(query.joins) + 1 + start_bindings, - expression_accumulator: %AshPostgres.Expr.ExprInfo{}, - in_group?: false, - calculations: %{}, - parent_resources: [], - aggregate_defs: %{}, - current_aggregate_name: :aggregate_0, - current_calculation_name: :calculation_0, - aggregate_names: %{}, - calculation_names: %{}, - context: context, - bindings: %{start_bindings => %{path: [], type: :root, source: resource}} - }) - end - @impl true def add_aggregates(query, aggregates, resource) do - AshPostgres.Aggregate.add_aggregates(query, aggregates, resource, true, 0) + AshSql.Aggregate.add_aggregates(query, aggregates, resource, true, 0) end @impl true def add_calculations(query, calculations, resource, select? \\ true) do - AshPostgres.Calculation.add_calculations(query, calculations, resource, 0, select?) - end - - @doc false - def merge_expr_accumulator(query, acc) do - update_in( - query.__ash_bindings__.expression_accumulator, - &AshPostgres.Expr.merge_accumulator(&1, acc) - ) - end - - @doc false - def get_binding(resource, path, query, type, name_match \\ nil) - - def get_binding(resource, path, %{__ash_bindings__: _} = query, type, name_match) do - types = List.wrap(type) - - Enum.find_value(query.__ash_bindings__.bindings, fn - {binding, %{path: candidate_path, type: binding_type} = data} -> - if binding_type in types do - if name_match do - if data[:name] == name_match do - if Ash.SatSolver.synonymous_relationship_paths?(resource, candidate_path, path) do - binding - end - end - else - if Ash.SatSolver.synonymous_relationship_paths?(resource, candidate_path, path) do - binding - else - false - end - end - end - - _ -> - nil - end) - end - - def get_binding(_, _, _, _, _), do: nil - - defp add_filter_expression(query, filter) do - filter - |> split_and_statements() - |> Enum.reduce(query, fn filter, query -> - {dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__) - - if is_nil(dynamic) do - query - else - query - |> Ecto.Query.where([], ^dynamic) - |> merge_expr_accumulator(acc) - end - end) - end - - @doc false - def split_and_statements(%Filter{expression: expression}) do - split_and_statements(expression) - end - - def split_and_statements(%BooleanExpression{op: :and, left: left, right: right}) do - split_and_statements(left) ++ split_and_statements(right) - end - - def split_and_statements(%Not{expression: %Not{expression: expression}}) do - split_and_statements(expression) - end - - def split_and_statements(%Not{ - expression: %BooleanExpression{op: :or, left: left, right: right} - }) do - split_and_statements(%BooleanExpression{ - op: :and, - left: %Not{expression: left}, - right: %Not{expression: right} - }) - end - - def split_and_statements(other), do: [other] - - @doc false - def add_binding(query, data, additional_bindings \\ 0) do - current = query.__ash_bindings__.current - bindings = query.__ash_bindings__.bindings - - new_ash_bindings = %{ - query.__ash_bindings__ - | bindings: Map.put(bindings, current, data), - current: current + 1 + additional_bindings - } - - %{query | __ash_bindings__: new_ash_bindings} + AshSql.Calculation.add_calculations(query, calculations, resource, 0, select?) end def add_known_binding(query, data, known_binding) do @@ -3307,29 +2602,6 @@ defmodule AshPostgres.DataLayer do end end - defp dynamic_repo(resource, %{__ash_bindings__: %{context: %{data_layer: %{repo: repo}}}}) do - repo || AshPostgres.DataLayer.Info.repo(resource, :read) - end - - defp dynamic_repo(resource, %struct{context: %{data_layer: %{repo: repo}}}) do - type = struct_to_repo_type(struct) - - repo || AshPostgres.DataLayer.Info.repo(resource, type) - end - - defp dynamic_repo(resource, %struct{}) do - AshPostgres.DataLayer.Info.repo(resource, struct_to_repo_type(struct)) - end - - defp struct_to_repo_type(struct) do - case struct do - Ash.Changeset -> :mutate - Ash.Query -> :read - Ecto.Query -> :read - Ecto.Changeset -> :mutate - end - end - defp resolve_source(resource, changeset) do if table = changeset.context[:data_layer][:table] do {table, resource} diff --git a/lib/expr.ex b/lib/expr.ex deleted file mode 100644 index 305f9e9d..00000000 --- a/lib/expr.ex +++ /dev/null @@ -1,2367 +0,0 @@ -defmodule AshPostgres.Expr do - @moduledoc false - - alias Ash.Filter - alias Ash.Query.{BooleanExpression, Exists, Not, Ref} - alias Ash.Query.Operator.IsNil - - alias Ash.Query.Function.{ - Ago, - At, - CompositeType, - Contains, - CountNils, - DateAdd, - DateTimeAdd, - Error, - Fragment, - FromNow, - GetPath, - If, - Lazy, - Length, - Now, - Round, - StringDowncase, - StringJoin, - StringLength, - StringSplit, - StringTrim, - Today, - Type - } - - alias AshPostgres.Functions.{ILike, Like, TrigramSimilarity, VectorCosineDistance} - - require Ecto.Query - - defmodule ExprInfo do - @moduledoc false - defstruct has_error?: false - end - - def dynamic_expr(query, expr, bindings, embedded? \\ false, type \\ nil, acc \\ %ExprInfo{}) - - def dynamic_expr(_query, %Filter{expression: nil}, _bindings, _embedded?, _type, acc) do - # a nil filter means everything - {true, acc} - end - - def dynamic_expr(query, %Filter{expression: expression}, bindings, embedded?, type, acc) do - dynamic_expr(query, expression, bindings, embedded?, type, acc) - end - - def dynamic_expr(_, true, _, _, _, acc), do: {true, acc} - def dynamic_expr(_, false, _, _, _, acc), do: {false, acc} - - def dynamic_expr(query, expression, bindings, embedded?, type, acc) do - do_dynamic_expr(query, expression, bindings, embedded?, acc, type) - end - - defp do_dynamic_expr(query, expr, bindings, embedded?, acc, type \\ nil) - - defp do_dynamic_expr(_, {:embed, other}, _bindings, _true, acc, _type) do - {other, acc} - end - - defp do_dynamic_expr(query, %Not{expression: expression}, bindings, embedded?, acc, _type) do - {new_expression, acc} = - do_dynamic_expr(query, expression, bindings, embedded?, acc, :boolean) - - {Ecto.Query.dynamic(not (^new_expression)), acc} - end - - defp do_dynamic_expr( - query, - %TrigramSimilarity{arguments: [arg1, arg2], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {arg1, acc} = - do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) - - {arg2, acc} = - do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) - - {Ecto.Query.dynamic(fragment("similarity(?, ?)", ^arg1, ^arg2)), acc} - end - - defp do_dynamic_expr( - query, - %VectorCosineDistance{arguments: [arg1, arg2], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {arg1, acc} = - do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) - - {arg2, acc} = - do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) - - {Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)), acc} - end - - defp do_dynamic_expr( - query, - %Like{arguments: [arg1, arg2], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {arg1, acc} = - do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) - - {arg2, acc} = - do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) - - {Ecto.Query.dynamic(like(^arg1, ^arg2)), acc} - end - - defp do_dynamic_expr( - query, - %ILike{arguments: [arg1, arg2], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - {arg1, acc} = - do_dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, acc, :string) - - {arg2, acc} = - do_dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, acc, :string) - - type = - if type != Ash.Type.Boolean do - type - end - - { - Ecto.Query.dynamic(ilike(^arg1, ^arg2)) - |> maybe_type(type, query), - acc - } - end - - defp do_dynamic_expr( - query, - %IsNil{left: left, right: true, embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {left_expr, acc} = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) - - {Ecto.Query.dynamic(is_nil(^left_expr)), acc} - end - - defp do_dynamic_expr( - query, - %IsNil{left: left, right: false, embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {left_expr, acc} = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) - - {Ecto.Query.dynamic(not is_nil(^left_expr)), acc} - end - - defp do_dynamic_expr( - query, - %IsNil{left: left, right: right, embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {left_expr, acc} = do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) - - {right_expr, acc} = - do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc, :boolean) - - {Ecto.Query.dynamic(is_nil(^left_expr) == ^right_expr), acc} - end - - defp do_dynamic_expr( - _query, - %Lazy{arguments: [{m, f, a}]}, - _bindings, - _embedded?, - acc, - _type - ) do - {apply(m, f, a), acc} - end - - defp do_dynamic_expr( - query, - %Ago{arguments: [left, right], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) - when is_binary(right) or is_atom(right) do - {left, acc} = - do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, :integer) - - {Ecto.Query.dynamic( - fragment("(?)", datetime_add(^DateTime.utc_now(), ^left * -1, ^to_string(right))) - ), acc} - end - - defp do_dynamic_expr( - query, - %At{arguments: [left, right], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - {left, acc} = - do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, :integer) - - {right, acc} = - do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc, :integer) - - expr = - if is_integer(right) do - Ecto.Query.dynamic(fragment("(?)[?]", ^left, ^(right + 1))) - else - Ecto.Query.dynamic(fragment("(?)[? + 1]", ^left, ^right)) - end - - {expr, acc} - end - - defp do_dynamic_expr( - query, - %FromNow{arguments: [left, right], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) - when is_binary(right) or is_atom(right) do - {left, acc} = - do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, :integer) - - {Ecto.Query.dynamic( - fragment("(?)", datetime_add(^DateTime.utc_now(), ^left, ^to_string(right))) - ), acc} - end - - defp do_dynamic_expr( - query, - %DateTimeAdd{arguments: [datetime, amount, interval], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) - when is_binary(interval) or is_atom(interval) do - {datetime, acc} = do_dynamic_expr(query, datetime, bindings, pred_embedded? || embedded?, acc) - - {amount, acc} = - do_dynamic_expr(query, amount, bindings, pred_embedded? || embedded?, acc, :integer) - - {Ecto.Query.dynamic(fragment("(?)", datetime_add(^datetime, ^amount, ^to_string(interval)))), - acc} - end - - defp do_dynamic_expr( - query, - %DateAdd{arguments: [date, amount, interval], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) - when is_binary(interval) or is_atom(interval) do - {date, acc} = do_dynamic_expr(query, date, bindings, pred_embedded? || embedded?, acc) - - {amount, acc} = - do_dynamic_expr(query, amount, bindings, pred_embedded? || embedded?, acc, :integer) - - {Ecto.Query.dynamic(fragment("(?)", datetime_add(^date, ^amount, ^to_string(interval)))), acc} - end - - defp do_dynamic_expr( - query, - %GetPath{ - arguments: [ - %Ref{attribute: %Ash.Resource.Aggregate{} = aggregate, resource: resource} = left, - right - ], - embedded?: pred_embedded? - }, - bindings, - embedded?, - acc, - _ - ) - when is_list(right) do - attribute = - case aggregate.field do - nil -> - nil - - %{} = field -> - field - - field -> - related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - Ash.Resource.Info.attribute(related, field) - end - - attribute_type = - if attribute do - attribute.type - end - - attribute_constraints = - if attribute do - attribute.constraints - end - - {:ok, type, constraints} = - Ash.Query.Aggregate.kind_to_type(aggregate.kind, attribute_type, attribute_constraints) - - type - |> Ash.Resource.Info.aggregate_type(aggregate) - |> split_at_paths(constraints, right) - |> Enum.reduce(do_dynamic_expr(query, left, bindings, embedded?, acc), fn data, {expr, acc} -> - do_get_path(query, expr, data, bindings, embedded?, pred_embedded?, acc) - end) - end - - defp do_dynamic_expr( - query, - %GetPath{ - arguments: [%Ref{attribute: %{type: type, constraints: constraints}} = left, right], - embedded?: pred_embedded? - }, - bindings, - embedded?, - acc, - _ - ) - when is_list(right) do - type - |> split_at_paths(constraints, right) - |> Enum.reduce(do_dynamic_expr(query, left, bindings, embedded?, acc), fn data, {expr, acc} -> - do_get_path(query, expr, data, bindings, embedded?, pred_embedded?, acc) - end) - end - - defp do_dynamic_expr( - query, - %Contains{arguments: [left, %Ash.CiString{} = right], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - if "citext" in AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate).installed_extensions() do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(strpos((", - expr: left, - raw: "::citext), (", - expr: right, - raw: ")) > 0)" - ] - }, - bindings, - embedded?, - acc, - type - ) - else - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(strpos(lower(", - expr: left, - raw: "), lower(", - expr: right, - raw: ")) > 0)" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - end - - defp do_dynamic_expr( - query, - %CountNils{arguments: [list], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - if is_list(list) do - list = - Enum.map(list, fn item -> - %Ash.Query.Operator.IsNil{left: item, right: true} - end) - - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(SELECT COUNT(*) FROM unnest(", - expr: list, - raw: ") AS item WHERE item IS TRUE)" - ] - }, - bindings, - embedded?, - acc, - type - ) - else - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(SELECT COUNT(*) FROM unnest(", - expr: list, - raw: ") AS item WHERE item IS NULL)" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - end - - defp do_dynamic_expr( - query, - %Contains{arguments: [left, right], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(strpos((", - expr: left, - raw: "), (", - expr: right, - raw: ")) > 0)" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %Length{arguments: [list], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "array_length((", - expr: list, - raw: "), 1)" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %If{arguments: [condition, when_true, when_false], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - [condition_type, when_true_type, when_false_type] = - case AshPostgres.Types.determine_types(If, [condition, when_true, when_false]) do - [condition_type, when_true] -> - [condition_type, when_true, nil] - - [condition_type, when_true, when_false] -> - [condition_type, when_true, when_false] - end - |> case do - [condition_type, nil, nil] -> - [condition_type, type, type] - - [condition_type, when_true, nil] -> - [condition_type, when_true, type] - - [condition_type, nil, when_false] -> - [condition_type, type, when_false] - - [condition_type, when_true, when_false] -> - [condition_type, when_true, when_false] - end - - {condition, acc} = - do_dynamic_expr( - query, - condition, - bindings, - pred_embedded? || embedded?, - acc, - condition_type - ) - - {when_true, acc} = - do_dynamic_expr( - query, - when_true, - bindings, - pred_embedded? || embedded?, - acc, - when_true_type - ) - - {additional_cases, when_false, acc} = - extract_cases( - query, - when_false, - bindings, - pred_embedded? || embedded?, - acc, - when_false_type - ) - - additional_case_fragments = - additional_cases - |> Enum.flat_map(fn {condition, when_true} -> - [ - raw: " WHEN ", - casted_expr: condition, - raw: " THEN ", - casted_expr: when_true - ] - end) - - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: - [ - raw: "(CASE WHEN ", - casted_expr: condition, - raw: " THEN ", - casted_expr: when_true - ] ++ - additional_case_fragments ++ - [ - raw: " ELSE ", - casted_expr: when_false, - raw: " END)" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %StringJoin{arguments: [values, joiner], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: - Enum.reduce(values, [raw: "(concat_ws(", expr: joiner], fn value, frag_acc -> - frag_acc ++ [raw: ", ", expr: value] - end) ++ [raw: "))"] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %StringSplit{arguments: [string, delimiter, options], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - if options[:trim?] do - require_ash_functions!(query, "string_split(..., trim?: true)") - - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "ash_trim_whitespace(string_to_array(", - expr: string, - raw: ", NULLIF(", - expr: delimiter, - raw: ", '')))" - ] - }, - bindings, - embedded?, - acc, - type - ) - else - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "string_to_array(", - expr: string, - raw: ", NULLIF(", - expr: delimiter, - raw: ", ''))" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - end - - defp do_dynamic_expr( - query, - %StringJoin{arguments: [values], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: - [raw: "(concat("] ++ - (values - |> Enum.reduce([], fn value, acc -> - acc ++ [expr: value] - end) - |> Enum.intersperse({:raw, ", "})) ++ - [raw: "))"] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %StringLength{arguments: [value], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [raw: "length(", expr: value, raw: ")"] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %StringDowncase{arguments: [value], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [raw: "lower(", expr: value, raw: ")"] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %StringTrim{arguments: [value], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "REGEXP_REPLACE(REGEXP_REPLACE(", - expr: value, - raw: ", '\s+$', ''), '^\s+', '')" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - - # Sorry :( - # This is bad to do, but is the only reasonable way I could find. - defp do_dynamic_expr( - query, - %Fragment{arguments: arguments, embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - arguments = - case arguments do - [{:raw, _} | _] -> - arguments - - arguments -> - [{:raw, ""} | arguments] - end - - arguments = - case List.last(arguments) do - nil -> - arguments - - {:raw, _} -> - arguments - - _ -> - arguments ++ [{:raw, ""}] - end - - {params, fragment_data, _, acc} = - Enum.reduce(arguments, {[], [], 0, acc}, fn - {:raw, str}, {params, fragment_data, count, acc} -> - {params, [{:raw, str} | fragment_data], count, acc} - - {:casted_expr, dynamic}, {params, fragment_data, count, acc} -> - {item, params, count} = - {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} - - {params, [{:expr, item} | fragment_data], count, acc} - - {:expr, expr}, {params, fragment_data, count, acc} -> - {dynamic, acc} = - do_dynamic_expr(query, expr, bindings, pred_embedded? || embedded?, acc) - - {item, params, count} = - {{:^, [], [count]}, [{dynamic, :any} | params], count + 1} - - {params, [{:expr, item} | fragment_data], count, acc} - end) - - {%Ecto.Query.DynamicExpr{ - fun: fn _query -> - {{:fragment, [], Enum.reverse(fragment_data)}, Enum.reverse(params), [], %{}} - end, - binding: [], - file: __ENV__.file, - line: __ENV__.line - }, acc} - end - - defp do_dynamic_expr( - query, - %BooleanExpression{op: op, left: left, right: right}, - bindings, - embedded?, - acc, - _type - ) do - {left_expr, acc} = do_dynamic_expr(query, left, bindings, embedded?, acc, :boolean) - {right_expr, acc} = do_dynamic_expr(query, right, bindings, embedded?, acc, :boolean) - - expr = - case op do - :and -> - Ecto.Query.dynamic(^left_expr and ^right_expr) - - :or -> - Ecto.Query.dynamic(^left_expr or ^right_expr) - end - - {expr, acc} - end - - defp do_dynamic_expr( - query, - %Ash.Query.Function.Minus{arguments: [arg], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - [determined_type] = AshPostgres.Types.determine_types(Ash.Query.Function.Minus, [arg]) - - {expr, acc} = - do_dynamic_expr( - query, - arg, - bindings, - pred_embedded? || embedded?, - acc, - determined_type || type - ) - - {Ecto.Query.dynamic(-(^expr)), acc} - end - - # Honestly we need to either 1. not type cast or 2. build in type compatibility concepts - # instead of `:same` we need an `ANY COMPATIBLE` equivalent. - @cast_operands_for [:<>] - - defp do_dynamic_expr( - query, - %mod{ - __predicate__?: _, - left: left, - right: right, - embedded?: pred_embedded?, - operator: operator - }, - bindings, - embedded?, - acc, - type - ) do - [left_type, right_type] = - mod - |> AshPostgres.Types.determine_types([left, right]) - - {left_expr, acc} = - if left_type && operator in @cast_operands_for do - {left_expr, acc} = - do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc) - - {Ecto.Query.dynamic(type(^left_expr, ^left_type)), acc} - else - do_dynamic_expr(query, left, bindings, pred_embedded? || embedded?, acc, left_type) - end - - {right_expr, acc} = - if right_type && operator in @cast_operands_for do - {right_expr, acc} = - do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc) - - {Ecto.Query.dynamic(type(^right_expr, ^right_type)), acc} - else - do_dynamic_expr(query, right, bindings, pred_embedded? || embedded?, acc, right_type) - end - - case operator do - :== -> - {Ecto.Query.dynamic(^left_expr == ^right_expr), acc} - - :!= -> - {Ecto.Query.dynamic(^left_expr != ^right_expr), acc} - - :> -> - {Ecto.Query.dynamic(^left_expr > ^right_expr), acc} - - :< -> - {Ecto.Query.dynamic(^left_expr < ^right_expr), acc} - - :>= -> - {Ecto.Query.dynamic(^left_expr >= ^right_expr), acc} - - :<= -> - {Ecto.Query.dynamic(^left_expr <= ^right_expr), acc} - - :in -> - {Ecto.Query.dynamic(^left_expr in ^right_expr), acc} - - :+ -> - {Ecto.Query.dynamic(^left_expr + ^right_expr), acc} - - :- -> - {Ecto.Query.dynamic(^left_expr - ^right_expr), acc} - - :/ -> - {Ecto.Query.dynamic(type(^left_expr, :decimal) / type(^right_expr, :decimal)), acc} - - :* -> - {Ecto.Query.dynamic(^left_expr * ^right_expr), acc} - - :<> -> - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "(", - casted_expr: left_expr, - raw: " || ", - casted_expr: right_expr, - raw: ")" - ] - }, - bindings, - embedded?, - acc, - type - ) - - :|| -> - require_ash_functions!(query, "||") - - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "ash_elixir_or(", - casted_expr: left_expr, - raw: ", ", - casted_expr: right_expr, - raw: ")" - ] - }, - bindings, - embedded?, - acc, - type - ) - - :&& -> - require_ash_functions!(query, "&&") - - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "ash_elixir_and(", - casted_expr: left_expr, - raw: ", ", - casted_expr: right_expr, - raw: ")" - ] - }, - bindings, - embedded?, - acc, - type - ) - - other -> - raise "Operator not implemented #{other}" - end - end - - defp do_dynamic_expr(query, %MapSet{} = mapset, bindings, embedded?, acc, type) do - do_dynamic_expr(query, Enum.to_list(mapset), bindings, embedded?, acc, type) - end - - defp do_dynamic_expr( - query, - %Ash.CiString{string: string} = expression, - bindings, - embedded?, - acc, - type - ) do - {string, acc} = do_dynamic_expr(query, string, bindings, embedded?, acc) - - require_extension!(query.__ash_bindings__.resource, "citext", expression) - - do_dynamic_expr( - query, - %Fragment{ - embedded?: embedded?, - arguments: [ - raw: "", - casted_expr: string, - raw: "::citext" - ] - }, - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %Ref{ - attribute: %Ash.Query.Calculation{} = calculation, - relationship_path: relationship_path - } = type_expr, - bindings, - embedded?, - acc, - _type - ) do - calculation = %{calculation | load: calculation.name} - - type = - AshPostgres.Types.parameterized_type( - calculation.type, - Map.get(calculation, :constraints, []) - ) - - validate_type!(query, type, type_expr) - resource = Ash.Resource.Info.related(bindings.resource, relationship_path) - - case Ash.Filter.hydrate_refs( - calculation.module.expression(calculation.opts, calculation.context), - %{ - resource: resource, - aggregates: %{}, - calculations: %{}, - public?: false - } - ) do - {:ok, expression} -> - expression = - Ash.Filter.move_to_relationship_path( - expression, - relationship_path - ) - - expression = - Ash.Actions.Read.add_calc_context_to_filter( - expression, - calculation.context.actor, - calculation.context.authorize?, - calculation.context.tenant, - calculation.context.tracer, - nil - ) - - do_dynamic_expr( - query, - expression, - bindings, - embedded?, - acc, - type - ) - - {:error, error} -> - raise """ - Failed to hydrate references for resource #{inspect(resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))} - - #{inspect(error)} - """ - end - end - - defp do_dynamic_expr( - query, - %Ref{ - attribute: %Ash.Query.Aggregate{ - kind: :exists, - relationship_path: agg_relationship_path, - query: agg_query, - join_filters: join_filters - }, - relationship_path: ref_relationship_path - }, - bindings, - embedded?, - acc, - type - ) do - filter = - if is_nil(agg_query.filter) do - true - else - agg_query.filter - end - - do_dynamic_expr( - query, - %Ash.Query.Exists{ - path: agg_relationship_path, - expr: filter, - at_path: ref_relationship_path - } - |> Map.put(:__join_filters__, join_filters), - bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %Ref{attribute: %Ash.Query.Aggregate{} = aggregate} = ref, - bindings, - _embedded?, - acc, - _type - ) do - %{attribute: aggregate} = - ref = - case bindings.aggregate_names[aggregate.name] do - nil -> - ref - - name -> - %{ref | attribute: %{aggregate | name: name}} - end - - related = Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path) - - first_optimized_aggregate? = - AshPostgres.Aggregate.optimizable_first_aggregate?(related, aggregate) - - {ref_binding, field_name, value, acc} = - if first_optimized_aggregate? do - ref = %{ - ref - | attribute: %Ash.Resource.Attribute{name: :fake}, - relationship_path: ref.relationship_path ++ aggregate.relationship_path - } - - ref_binding = ref_binding(ref, bindings) - - if is_nil(ref_binding) do - raise "Error while building reference: #{inspect(ref)}" - end - - ref = - %Ash.Query.Ref{ - attribute: - AshPostgres.Aggregate.aggregate_field( - aggregate, - Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path) - ), - relationship_path: ref.relationship_path, - resource: query.__ash_bindings__.resource - } - - ref = - Ash.Actions.Read.add_calc_context_to_filter( - ref, - aggregate.context[:actor], - aggregate.context[:authorize?], - aggregate.context[:tenant], - aggregate.context[:tracer], - nil - ) - - {value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc) - - case aggregate.field do - %{name: name} -> name - field -> field - end - - {ref_binding, aggregate.field, value, acc} - else - ref_binding = ref_binding(ref, bindings) - - if is_nil(ref_binding) do - raise "Error while building reference: #{inspect(ref)}" - end - - {ref_binding, aggregate.name, nil, acc} - end - - field_name = - if is_binary(field_name) do - new_field_name = - query.__ash_bindings__.aggregate_names[field_name] - - unless new_field_name do - raise "Unbound aggregate field: #{inspect(field_name)}" - end - - new_field_name - else - field_name - end - - expr = - if value do - value - else - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(field(parent_as(^ref_binding), ^field_name)) - else - Ecto.Query.dynamic(field(as(^ref_binding), ^field_name)) - end - end - - type = AshPostgres.Types.parameterized_type(aggregate.type, aggregate.constraints) - validate_type!(query, type, ref) - - type = - if type && aggregate.kind == :list do - {:array, type} - else - type - end - - coalesced = - if is_nil(aggregate.default_value) do - expr - else - if type do - Ecto.Query.dynamic(coalesce(^expr, type(^aggregate.default_value, ^type))) - else - Ecto.Query.dynamic(coalesce(^expr, ^aggregate.default_value)) - end - end - - if type do - {Ecto.Query.dynamic(type(^coalesced, ^type)), acc} - else - {coalesced, acc} - end - end - - defp do_dynamic_expr( - query, - %Round{arguments: [num | rest], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) do - precision = Enum.at(rest, 0) || 1 - - frag = - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "ROUND(", - expr: num, - raw: ", ", - expr: precision, - raw: ")" - ] - } - - do_dynamic_expr(query, frag, bindings, pred_embedded? || embedded?, acc) - end - - defp do_dynamic_expr( - query, - %Type{ - arguments: [ - %Type{arguments: [_, type, constraints]} = nested_call, - type, - constraints - ] - }, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr(query, nested_call, bindings, embedded?, acc, type) - end - - defp do_dynamic_expr( - query, - %Type{arguments: [arg1, arg2, constraints]}, - bindings, - embedded?, - acc, - _type - ) do - arg2 = Ash.Type.get_type(arg2) - arg1 = maybe_uuid_to_binary(arg2, arg1, arg1) - type = AshPostgres.Types.parameterized_type(arg2, constraints) - - if type do - {expr, acc} = do_dynamic_expr(query, arg1, bindings, embedded?, acc, type) - - case {type, expr} do - {{:parameterized, Ash.Type.Map.EctoType, []}, %Ecto.Query.DynamicExpr{}} -> - {expr, acc} - - _ -> - {Ecto.Query.dynamic(type(^expr, ^type)), acc} - end - else - do_dynamic_expr(query, arg1, bindings, embedded?, acc, type) - end - end - - defp do_dynamic_expr( - query, - %CompositeType{arguments: [arg1, arg2, constraints], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - _type - ) - when is_map(arg1) do - type = Ash.Type.get_type(arg2) - - composite_keys = Ash.Type.composite_types(type, constraints) - - type = AshPostgres.Types.parameterized_type(type, constraints) - - values = - composite_keys - |> Enum.map(fn config -> - key = elem(config, 0) - {:expr, Map.get(arg1, key)} - end) - |> Enum.intersperse({:raw, ","}) - - frag = - %Fragment{ - embedded?: pred_embedded?, - arguments: - [ - raw: "ROW(" - ] ++ - values ++ - [ - raw: ")" - ] - } - - {frag, acc} = - do_dynamic_expr(query, frag, bindings, embedded?, acc) - - {Ecto.Query.dynamic(type(^frag, ^type)), acc} - end - - defp do_dynamic_expr( - query, - %Now{embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - DateTime.utc_now(), - bindings, - embedded? || pred_embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %Today{embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type - ) do - do_dynamic_expr( - query, - Date.utc_today(), - bindings, - embedded? || pred_embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %Ash.Query.Parent{expr: expr}, - bindings, - embedded?, - acc, - type - ) do - parent? = Map.get(bindings.parent_bindings, :parent_is_parent_as?, true) - new_bindings = Map.put(bindings.parent_bindings, :parent?, parent?) - - do_dynamic_expr( - %{ - query - | __ash_bindings__: new_bindings - }, - expr, - new_bindings, - embedded?, - acc, - type - ) - end - - defp do_dynamic_expr( - query, - %Error{arguments: [exception, input]} = value, - bindings, - embedded?, - acc, - type - ) do - require_ash_functions!(query, "error/2") - - acc = %{acc | has_error?: true} - - unless Keyword.keyword?(input) || is_map(input) do - raise "Input expression to `error` must be a map or keyword list" - end - - {encoded, acc} = - if Ash.Expr.expr?(input) do - frag_parts = - Enum.flat_map(input, fn {key, value} -> - if Ash.Expr.expr?(value) do - [ - expr: to_string(key), - raw: "::text, ", - expr: value, - raw: ", " - ] - else - [ - expr: to_string(key), - raw: "::text, ", - expr: value, - raw: "::jsonb, " - ] - end - end) - - frag_parts = - List.update_at(frag_parts, -1, fn {:raw, text} -> - {:raw, String.trim_trailing(text, ", ") <> "))"} - end) - - do_dynamic_expr( - query, - %Fragment{ - embedded?: false, - arguments: - [ - raw: "jsonb_build_object('exception', ", - expr: inspect(exception), - raw: "::text, 'input', jsonb_build_object(" - ] ++ - frag_parts - }, - bindings, - embedded?, - acc - ) - else - {Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}), acc} - end - - if type do - # This is a type hint, if we're raising an error, we tell it what the value - # type *would* be in this expression so that we can return a "NULL" of that type - # its weird, but there isn't any other way that I can tell :) - validate_type!(query, type, value) - - dynamic = - Ecto.Query.dynamic(type(^nil, ^type)) - - {Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb, ?)", ^encoded, ^dynamic)), acc} - else - {Ecto.Query.dynamic(fragment("ash_raise_error(?::jsonb)", ^encoded)), acc} - end - end - - defp do_dynamic_expr( - query, - %Exists{at_path: at_path, path: [first | rest], expr: expr} = exists, - bindings, - _embedded?, - acc, - _type - ) do - resource = Ash.Resource.Info.related(bindings.resource, at_path) - first_relationship = Ash.Resource.Info.relationship(resource, first) - - filter = Ash.Filter.move_to_relationship_path(expr, rest) - - filter = - exists - |> Map.get(:__join_filters__, %{}) - |> Map.fetch([first_relationship.name]) - |> case do - {:ok, join_filter} -> - Ash.Query.BooleanExpression.optimized_new( - :and, - filter, - Ash.Filter.move_to_relationship_path( - join_filter, - rest ++ [first_relationship.name] - ) - ) - - :error -> - filter - end - - filter = - exists - |> Map.get(:__join_filters__, %{}) - |> Map.delete([first_relationship.name]) - |> Enum.reduce(filter, fn {path, path_filter}, filter -> - path = Enum.drop(path, 1) - parent_path = :lists.droplast(path) - - Ash.Query.BooleanExpression.optimized_new( - :and, - filter, - Ash.Filter.move_to_relationship_path(path_filter, path) - ) - |> Ash.Filter.map(fn - %Ash.Query.Parent{expr: expr} -> - {:halt, Ash.Filter.move_to_relationship_path(expr, parent_path)} - - other -> - other - end) - end) - - {:ok, subquery} = - AshPostgres.Join.related_subquery(first_relationship, query, - filter: filter, - parent_resources: [ - query.__ash_bindings__.resource - | query.__ash_bindings__[:parent_resources] || [] - ], - return_subquery?: true, - on_subquery: fn subquery -> - subquery = - Ecto.Query.from(row in Ecto.Query.exclude(subquery, :select), - select: fragment("1") - ) - |> Map.put(:__ash_bindings__, subquery.__ash_bindings__) - - cond do - Map.get(first_relationship, :manual) -> - subquery - - Map.get(first_relationship, :no_attributes?) -> - subquery - - first_relationship.type == :many_to_many -> - source_ref = - ref_binding( - %Ref{ - attribute: - Ash.Resource.Info.attribute(resource, first_relationship.source_attribute), - relationship_path: at_path, - resource: resource - }, - bindings - ) - - through_relationship = - Ash.Resource.Info.relationship(resource, first_relationship.join_relationship) - - {:ok, through} = - AshPostgres.Join.related_subquery(through_relationship, query) - - Ecto.Query.from(destination in subquery, - join: through in ^through, - as: ^subquery.__ash_bindings__.current, - on: - field(through, ^first_relationship.destination_attribute_on_join_resource) == - field(destination, ^first_relationship.destination_attribute), - on: - field(parent_as(^source_ref), ^first_relationship.source_attribute) == - field(through, ^first_relationship.source_attribute_on_join_resource) - ) - - true -> - source_ref = - ref_binding( - %Ref{ - attribute: - Ash.Resource.Info.attribute(resource, first_relationship.source_attribute), - relationship_path: at_path, - resource: resource - }, - bindings - ) - - Ecto.Query.from(destination in subquery, - where: - field(parent_as(^source_ref), ^first_relationship.source_attribute) == - field(destination, ^first_relationship.destination_attribute) - ) - end - end - ) - - {Ecto.Query.dynamic(exists(subquery)), acc} - end - - defp do_dynamic_expr( - query, - %Ref{ - attribute: %Ash.Resource.Attribute{ - name: name, - type: attr_type, - constraints: constraints - } - } = ref, - bindings, - _embedded?, - acc, - expr_type - ) do - ref_binding = ref_binding(ref, bindings) - - if is_nil(ref_binding) do - raise "Error while building reference: #{inspect(ref)}" - end - - constraints = - if attr_type do - constraints - end - - expr = - case AshPostgres.Types.parameterized_type(attr_type || expr_type, constraints) do - nil -> - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(field(parent_as(^ref_binding), ^name)) - else - Ecto.Query.dynamic(field(as(^ref_binding), ^name)) - end - - type -> - validate_type!(query, type, ref) - - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(type(field(parent_as(^ref_binding), ^name), ^type)) - else - Ecto.Query.dynamic(type(field(as(^ref_binding), ^name), ^type)) - end - end - - {expr, acc} - end - - defp do_dynamic_expr( - query, - %Ref{attribute: %Ash.Resource.Aggregate{name: name}} = ref, - bindings, - _embedded?, - acc, - _expr_type - ) do - ref_binding = ref_binding(ref, bindings) - - if is_nil(ref_binding) do - raise "Error while building reference: #{inspect(ref)}" - end - - expr = - if query.__ash_bindings__[:parent?] do - Ecto.Query.dynamic(field(parent_as(^ref_binding), ^name)) - else - Ecto.Query.dynamic(field(as(^ref_binding), ^name)) - end - - {expr, acc} - end - - defp do_dynamic_expr(_query, %Ash.Vector{} = value, _bindings, _embedded?, acc, _type) do - {value, acc} - end - - defp do_dynamic_expr(query, value, bindings, embedded?, acc, type) - when is_map(value) and not is_struct(value) do - if bindings[:location] == :update && - Enum.any?(value, fn {key, value} -> - Ash.Expr.expr?(key) || Ash.Expr.expr?(value) - end) do - elements = - value - |> Enum.flat_map(fn {key, list_item} -> - if is_atom(key) do - [{:expr, %Ash.Query.Function.Type{arguments: [key, :atom, []]}}, {:expr, list_item}] - else - [ - {:expr, %Ash.Query.Function.Type{arguments: [key, :string, []]}}, - {:expr, list_item} - ] - end - end) - |> Enum.intersperse({:raw, ","}) - - do_dynamic_expr( - query, - %Fragment{ - embedded?: embedded?, - arguments: - [ - raw: "jsonb_build_object(" - ] ++ elements ++ [raw: ")"] - }, - bindings, - embedded?, - acc, - type - ) - else - {value, acc} = - Enum.reduce(value, {%{}, acc}, fn {key, value}, {map, acc} -> - {value, acc} = do_dynamic_expr(query, value, bindings, embedded?, acc) - - {Map.put(map, key, value), acc} - end) - - if embedded? do - {Ecto.Query.dynamic([], type(^value, :map)), acc} - else - {value, acc} - end - end - end - - defp do_dynamic_expr(query, other, bindings, true, acc, type) do - if other && is_atom(other) && !is_boolean(other) do - {to_string(other), acc} - else - if Ash.Expr.expr?(other) do - if is_list(other) do - list_expr(query, other, bindings, true, acc, type) - else - raise "Unsupported expression in AshPostgres query: #{inspect(other, structs: false)}" - end - else - maybe_sanitize_list(query, other, bindings, true, acc, type) - end - end - end - - defp do_dynamic_expr(query, value, bindings, embedded?, acc, {:in, type}) when is_list(value) do - list_expr(query, value, bindings, embedded?, acc, {:array, type}) - end - - defp do_dynamic_expr(query, value, bindings, embedded?, acc, type) - when not is_nil(value) and is_atom(value) and not is_boolean(value) do - do_dynamic_expr(query, to_string(value), bindings, embedded?, acc, type) - end - - defp do_dynamic_expr(query, value, bindings, false, acc, type) - when type == nil or type == :any do - if is_list(value) do - list_expr(query, value, bindings, false, acc, type) - else - maybe_sanitize_list(query, value, bindings, true, acc, type) - end - end - - defp do_dynamic_expr(query, value, bindings, false, acc, type) do - if Ash.Expr.expr?(value) do - if is_list(value) do - list_expr(query, value, bindings, false, acc, type) - else - raise "Unsupported expression in AshPostgres query: #{inspect(value, structs: false)}" - end - else - case maybe_sanitize_list(query, value, bindings, true, acc, type) do - {^value, acc} -> - if type do - validate_type!(query, type, value) - - {Ecto.Query.dynamic(type(^value, ^type)), acc} - else - {value, acc} - end - - {value, acc} -> - {value, acc} - end - end - end - - defp extract_cases( - query, - expr, - bindings, - embedded?, - acc, - type, - list_acc \\ [] - ) - - defp extract_cases( - query, - %If{arguments: [condition, when_true, when_false], embedded?: pred_embedded?}, - bindings, - embedded?, - acc, - type, - list_acc - ) do - [condition_type, when_true_type, when_false_type] = - case AshPostgres.Types.determine_types(If, [condition, when_true, when_false]) do - [condition_type, when_true] -> - [condition_type, when_true, nil] - - [condition_type, when_true, when_false] -> - [condition_type, when_true, when_false] - end - |> case do - [condition_type, nil, nil] -> - [condition_type, type, type] - - [condition_type, when_true, nil] -> - [condition_type, when_true, type] - - [condition_type, nil, when_false] -> - [condition_type, type, when_false] - - [condition_type, when_true, when_false] -> - [condition_type, when_true, when_false] - end - - {condition, acc} = - do_dynamic_expr( - query, - condition, - bindings, - pred_embedded? || embedded?, - acc, - condition_type - ) - - {when_true, acc} = - do_dynamic_expr( - query, - when_true, - bindings, - pred_embedded? || embedded?, - acc, - when_true_type - ) - - extract_cases( - query, - when_false, - bindings, - embedded?, - acc, - when_false_type, - [{condition, when_true} | list_acc] - ) - end - - defp extract_cases( - query, - other, - bindings, - embedded?, - acc, - type, - list_acc - ) do - {expr, acc} = - do_dynamic_expr( - query, - other, - bindings, - embedded?, - acc, - type - ) - - {Enum.reverse(list_acc), expr, acc} - end - - defp split_at_paths(type, constraints, next, acc \\ [{:bracket, [], nil, nil}]) - - defp split_at_paths(_type, _constraints, [], acc) do - acc - end - - defp split_at_paths({:array, type}, constraints, [next | rest], [first_acc | rest_acc]) - when is_integer(next) do - case first_acc do - {:bracket, path, nil, nil} -> - split_at_paths(type, constraints[:items] || [], rest, [ - {:bracket, [next | path], type, constraints} - | rest_acc - ]) - - {:dot, _field, _, _} -> - split_at_paths(type, constraints[:items] || [], rest, [ - {:bracket, [next], type, constraints}, - first_acc - | rest_acc - ]) - end - end - - defp split_at_paths(type, constraints, [next | rest], [first_acc | rest_acc]) - when is_atom(next) or is_binary(next) do - bracket_or_dot = - if type && Ash.Type.composite?(type, constraints) do - :dot - else - :bracket - end - - {next, type, constraints} = - cond do - type && Ash.Type.embedded_type?(type) -> - type = - if Ash.Type.NewType.new_type?(type) do - Ash.Type.NewType.subtype_of(type) - else - type - end - - %{type: type, constraints: constraints} = Ash.Resource.Info.attribute(type, next) - {next, type, constraints} - - type && Ash.Type.composite?(type, constraints) -> - condition = - if is_binary(next) do - fn {name, _type, _constraints} -> - to_string(name) == next - end - else - fn {name, _type, _constraints} -> - name == next - end - end - - case Enum.find(Ash.Type.composite_types(type, constraints), condition) do - nil -> - {next, nil, nil} - - {_, aliased_as, type, constraints} -> - {aliased_as, type, constraints} - - {name, type, constraints} -> - {name, type, constraints} - end - - true -> - {next, nil, nil} - end - - case bracket_or_dot do - :dot -> - case first_acc do - {:bracket, [], _, _} -> - split_at_paths(type, constraints, rest, [ - {bracket_or_dot, [next], type, constraints} | rest_acc - ]) - - {:bracket, path, nil, nil} -> - split_at_paths(type, constraints, rest, [ - {bracket_or_dot, [next], type, constraints}, - {:bracket, path, nil, nil} - | rest_acc - ]) - - {:dot, _path, _, _} -> - split_at_paths(type, constraints, rest, [ - {bracket_or_dot, [next], nil, nil}, - first_acc | rest_acc - ]) - end - - :bracket -> - case first_acc do - {:bracket, path, nil, nil} -> - split_at_paths(type, constraints, rest, [ - {bracket_or_dot, [next | path], type, constraints} - | rest_acc - ]) - - {:dot, _path, _, _} -> - split_at_paths(type, constraints, rest, [ - {bracket_or_dot, [next], nil, nil}, - first_acc | rest_acc - ]) - end - end - end - - defp list_expr(query, value, bindings, embedded?, acc, type) do - if !Enum.empty?(value) && - Enum.any?(value, fn value -> - Ash.Expr.expr?(value) || is_map(value) || is_list(value) - end) do - type = - case type do - {:array, type} -> type - {:in, type} -> type - _ -> nil - end - - elements = - Enum.map(value, fn list_item -> - if type do - {:expr, %Ash.Query.Function.Type{arguments: [list_item, type, []]}} - else - {:expr, list_item} - end - end) - |> Enum.intersperse({:raw, ","}) - - do_dynamic_expr( - query, - %Fragment{ - embedded?: embedded?, - arguments: - [ - raw: "ARRAY[" - ] ++ elements ++ [raw: "]"] - }, - bindings, - embedded?, - acc, - type - ) - else - type = - case type do - {:array, type} -> type - {:in, type} -> type - _ -> nil - end - - {params, exprs, _, acc} = - Enum.reduce(value, {[], [], 0, acc}, fn value, {params, data, count, acc} -> - case do_dynamic_expr(query, value, bindings, embedded?, acc, type) do - {%Ecto.Query.DynamicExpr{} = dynamic, acc} -> - result = - Ecto.Query.Builder.Dynamic.partially_expand( - :select, - query, - dynamic, - params, - count - ) - - expr = elem(result, 0) - new_params = elem(result, 1) - new_count = result |> Tuple.to_list() |> List.last() - - {new_params, [expr | data], new_count, acc} - - {other, acc} -> - {params, [other | data], count, acc} - end - end) - - {%Ecto.Query.DynamicExpr{ - fun: fn _query -> - {Enum.reverse(exprs), Enum.reverse(params), [], []} - end, - binding: [], - file: __ENV__.file, - line: __ENV__.line - }, acc} - end - end - - defp maybe_uuid_to_binary({:array, type}, value, _original_value) when is_list(value) do - Enum.map(value, &maybe_uuid_to_binary(type, &1, &1)) - end - - defp maybe_uuid_to_binary(type, value, original_value) - when type in [ - Ash.Type.UUID.EctoType, - :uuid - ] and is_binary(value) do - case Ecto.UUID.dump(value) do - {:ok, encoded} -> encoded - _ -> original_value - end - end - - defp maybe_uuid_to_binary(_type, _value, original_value), do: original_value - - @doc false - def validate_type!(%{__ash_bindings__: %{resource: resource}}, type, context) do - validate_type!(resource, type, context) - end - - def validate_type!(resource, type, context) do - case type do - {:parameterized, Ash.Type.CiStringWrapper.EctoType, _} -> - require_extension!(resource, "citext", context) - - :ci_string -> - require_extension!(resource, "citext", context) - - :citext -> - require_extension!(resource, "citext", context) - - _ -> - :ok - end - end - - defp maybe_type(dynamic, nil, _query), do: dynamic - - defp maybe_type(dynamic, type, query) do - validate_type!(query, type, type) - - Ecto.Query.dynamic(type(^dynamic, ^type)) - end - - defp maybe_sanitize_list(query, value, bindings, embedded?, acc, type) do - if is_list(value) do - value - |> Enum.reduce({[], acc}, fn item, {list, acc} -> - {new_item, acc} = do_dynamic_expr(query, item, bindings, embedded?, acc, type) - - {[new_item | list], acc} - end) - |> then(fn {list, acc} -> - {Enum.reverse(list), acc} - end) - else - {value, acc} - end - end - - defp ref_binding( - %{attribute: %Ash.Query.Aggregate{name: name}, relationship_path: relationship_path}, - bindings - ) do - Enum.find_value(bindings.bindings, fn {binding, data} -> - data.type == :aggregate && - data.path == relationship_path && - Enum.any?(data.aggregates, &(&1.name == name)) && binding - end) || - Enum.find_value(bindings.bindings, fn {binding, data} -> - data.type in [:inner, :left, :root] && - Ash.SatSolver.synonymous_relationship_paths?( - bindings.resource, - data.path, - relationship_path - ) && binding - end) - end - - defp ref_binding( - %{ - attribute: %Ash.Resource.Aggregate{name: name}, - relationship_path: relationship_path - }, - bindings - ) do - Enum.find_value(bindings.bindings, fn {binding, data} -> - data.type == :aggregate && - data.path == relationship_path && - Enum.any?(data.aggregates, &(&1.name == name)) && binding - end) - end - - defp ref_binding(%{attribute: %Ash.Resource.Attribute{}} = ref, bindings) do - Enum.find_value(bindings.bindings, fn {binding, data} -> - data.type in [:inner, :left, :root] && - Ash.SatSolver.synonymous_relationship_paths?( - bindings.resource, - data.path, - ref.relationship_path - ) && binding - end) - end - - defp do_get_path( - query, - expr, - {:bracket, path, type, constraints}, - bindings, - embedded?, - pred_embedded?, - acc - ) do - type = AshPostgres.Types.parameterized_type(type, constraints) - path = path |> Enum.reverse() |> Enum.map(&to_string/1) - - path_frags = - path - |> Enum.flat_map(fn item -> - [expr: item, raw: "::text,"] - end) - |> :lists.droplast() - |> Enum.concat(raw: "::text)") - - {expr, acc} = - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: - [ - raw: "jsonb_extract_path_text(", - expr: expr, - raw: "::jsonb," - ] ++ path_frags - }, - bindings, - embedded?, - acc - ) - - if type do - {Ecto.Query.dynamic(type(^expr, ^type)), acc} - else - {expr, acc} - end - end - - defp do_get_path( - query, - expr, - {:dot, [field], type, constraints}, - bindings, - embedded?, - pred_embedded?, - acc - ) - when is_atom(field) do - type = AshPostgres.Types.parameterized_type(type, constraints) - - {expr, acc} = - do_dynamic_expr( - query, - %Fragment{ - embedded?: pred_embedded?, - arguments: [ - raw: "((", - expr: expr, - raw: ").#{field})" - ] - }, - bindings, - embedded?, - acc - ) - - if type do - {Ecto.Query.dynamic(type(^expr, ^type)), acc} - else - {expr, acc} - end - end - - defp require_ash_functions!(query, operator) do - installed_extensions = - AshPostgres.DataLayer.Info.repo(query.__ash_bindings__.resource, :mutate).installed_extensions() - - unless "ash-functions" in installed_extensions do - raise """ - Cannot use `#{operator}` without adding the extension `ash-functions` to your repo. - - Add it to the list in `installed_extensions/0` and generate migrations. - """ - end - end - - defp require_extension!(resource, extension, context) do - repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) - - unless extension in repo.installed_extensions() do - raise Ash.Error.Query.InvalidExpression, - expression: context, - message: - "The #{extension} extension needs to be installed before #{inspect(context)} can be used. Please add \"#{extension}\" to the list of installed_extensions in #{inspect(repo)}." - end - end - - @doc false - def set_parent_path(query, parent, parent_is_parent_as? \\ true) do - # This is a stupid name. Its actually the path we *remove* when stepping up a level. I.e the child's path - Map.update!(query, :__ash_bindings__, fn ash_bindings -> - ash_bindings - |> Map.put( - :parent_bindings, - parent.__ash_bindings__ |> Map.put(:parent_is_parent_as?, parent_is_parent_as?) - ) - |> Map.put(:parent_resources, [ - parent.__ash_bindings__.resource | parent.__ash_bindings__[:parent_resources] || [] - ]) - end) - end - - @doc false - def merge_accumulator(%ExprInfo{has_error?: left_has_error?}, %ExprInfo{ - has_error?: right_has_error? - }) do - %ExprInfo{has_error?: left_has_error? || right_has_error?} - end -end diff --git a/lib/join.ex b/lib/join.ex deleted file mode 100644 index 1f85aedb..00000000 --- a/lib/join.ex +++ /dev/null @@ -1,792 +0,0 @@ -defmodule AshPostgres.Join do - @moduledoc false - import Ecto.Query, only: [from: 2, subquery: 1] - - alias Ash.Query.{BooleanExpression, Not, Ref} - - @known_inner_join_operators [ - Eq, - GreaterThan, - GreaterThanOrEqual, - In, - LessThanOrEqual, - LessThan, - NotEq - ] - |> Enum.map(&Module.concat(Ash.Query.Operator, &1)) - - @known_inner_join_functions [ - Ago, - Contains - ] - |> Enum.map(&Module.concat(Ash.Query.Function, &1)) - - @known_inner_join_predicates @known_inner_join_functions ++ @known_inner_join_operators - - def join_all_relationships( - query, - filter, - opts \\ [], - relationship_paths \\ nil, - path \\ [], - source \\ nil, - sort? \\ true, - join_filters \\ nil, - parent_bindings \\ nil, - no_inner_join? \\ false - ) - - # simple optimization for common cases - def join_all_relationships( - query, - filter, - _opts, - relationship_paths, - _path, - _source, - _sort?, - _join_filters, - _parent_bindings, - _no_inner_join? - ) - when is_nil(relationship_paths) and filter in [nil, true, false] do - {:ok, query} - end - - def join_all_relationships( - query, - filter, - opts, - relationship_paths, - path, - source, - sort?, - join_filters, - parent_query, - no_inner_join? - ) do - relationship_paths = - cond do - relationship_paths -> - relationship_paths - - opts[:no_this?] -> - filter - |> Ash.Filter.map(fn - %Ash.Query.Parent{} -> - nil - - other -> - other - end) - |> Ash.Filter.relationship_paths() - |> to_joins(filter, query.__ash_bindings__.resource) - - true -> - filter - |> Ash.Filter.relationship_paths() - |> to_joins(filter, query.__ash_bindings__.resource) - end - - Enum.reduce_while(relationship_paths, {:ok, query}, fn - {_join_type, []}, {:ok, query} -> - {:cont, {:ok, query}} - - {join_type, [relationship | rest_rels]}, {:ok, query} -> - join_type = - if no_inner_join? do - :left - else - join_type - end - - source = source || relationship.source - - current_path = path ++ [relationship] - - current_join_type = join_type - - look_for_join_types = - case join_type do - :left -> - [:left, :inner] - - :inner -> - [:left, :inner] - - other -> - [other] - end - - binding = - get_binding(source, Enum.map(current_path, & &1.name), query, look_for_join_types) - - # We can't reuse joins if we're adding filters/have a separate parent binding - if is_nil(join_filters) && is_nil(parent_query) && binding do - case join_all_relationships( - query, - filter, - opts, - [{join_type, rest_rels}], - current_path, - source, - sort? - ) do - {:ok, query} -> - {:cont, {:ok, query}} - - {:error, error} -> - {:halt, {:error, error}} - end - else - case join_relationship( - set_parent_bindings(query, parent_query), - relationship, - Enum.map(path, & &1.name), - current_join_type, - source, - filter, - sort?, - Ash.Filter.move_to_relationship_path( - join_filters[Enum.map(current_path, & &1.name)], - [relationship.name] - ) - ) do - {:ok, joined_query} -> - joined_query_with_distinct = add_distinct(relationship, join_type, joined_query) - - case join_all_relationships( - joined_query_with_distinct, - filter, - opts, - [{join_type, rest_rels}], - current_path, - source, - sort?, - join_filters, - joined_query - ) do - {:ok, query} -> - {:cont, {:ok, query}} - - {:error, error} -> - {:halt, {:error, error}} - end - - {:error, error} -> - {:halt, {:error, error}} - end - end - end) - end - - defp set_parent_bindings(query, parent_query) do - if parent_query do - AshPostgres.Expr.set_parent_path(query, parent_query, false) - else - query - end - end - - defp to_joins(paths, filter, resource) do - paths - |> Enum.reject(&(&1 == [])) - |> Enum.map(fn path -> - if can_inner_join?(path, filter) do - {:inner, - AshPostgres.Join.relationship_path_to_relationships( - resource, - path - )} - else - {:left, - AshPostgres.Join.relationship_path_to_relationships( - resource, - path - )} - end - end) - end - - def relationship_path_to_relationships(resource, path, acc \\ []) - def relationship_path_to_relationships(_resource, [], acc), do: Enum.reverse(acc) - - def relationship_path_to_relationships(resource, [name | rest], acc) do - relationship = Ash.Resource.Info.relationship(resource, name) - - if !relationship do - raise "no such relationship #{inspect(resource)}.#{name}" - end - - relationship_path_to_relationships(relationship.destination, rest, [relationship | acc]) - end - - def related_subquery( - relationship, - root_query, - opts \\ [] - ) do - on_parent_expr = Keyword.get(opts, :on_parent_expr, & &1) - on_subquery = Keyword.get(opts, :on_subquery, & &1) - - with {:ok, query} <- related_query(relationship, root_query, opts) do - has_parent_expr? = - !!query.__ash_bindings__.context[:data_layer][:has_parent_expr?] || - not is_nil(query.limit) - - query = - if has_parent_expr? do - on_parent_expr.(query) - else - query - end - - query = on_subquery.(query) - - query = - if opts[:return_subquery?] do - subquery(query) - else - if Enum.empty?(query.joins) && Enum.empty?(query.order_bys) && Enum.empty?(query.wheres) do - query - else - from(row in subquery(query), as: ^0) - |> AshPostgres.DataLayer.default_bindings(relationship.destination) - |> AshPostgres.DataLayer.merge_expr_accumulator( - query.__ash_bindings__.expression_accumulator - ) - |> Map.update!( - :__ash_bindings__, - fn bindings -> - bindings - |> Map.put(:current, query.__ash_bindings__.current) - |> put_in([:context, :data_layer], %{ - has_parent_expr?: has_parent_expr? - }) - end - ) - end - end - - {:ok, query} - end - end - - defp related_query(relationship, query, opts) do - sort? = Keyword.get(opts, :sort?, false) - filter = Keyword.get(opts, :filter, nil) - parent_resources = Keyword.get(opts, :parent_stack, [relationship.source]) - - read_action = - relationship.read_action || - Ash.Resource.Info.primary_action!(relationship.destination, :read).name - - context = query.__ash_bindings__.context - - relationship.destination - |> Ash.Query.new() - |> Ash.Query.set_context(context) - |> Ash.Query.set_context(%{data_layer: %{table: nil}}) - |> Ash.Query.set_context(relationship.context) - |> Ash.Query.do_filter(relationship.filter, parent_stack: parent_resources) - |> Ash.Query.do_filter(filter, parent_stack: parent_resources) - |> Ash.Query.for_read(read_action, %{}, - actor: context[:private][:actor], - tenant: context[:private][:tenant] - ) - |> Ash.Query.unset([:sort, :distinct, :select, :limit, :offset]) - |> limit_from_many(relationship) - |> then(fn query -> - if sort? do - Ash.Query.sort(query, relationship.sort) - else - Ash.Query.unset(query, :sort) - end - end) - |> set_has_parent_expr_context(relationship) - |> case do - %{valid?: true} = related_query -> - Ash.Query.data_layer_query( - Ash.Query.set_context(related_query, %{ - data_layer: %{parent_bindings: query.__ash_bindings__} - }) - ) - |> case do - {:ok, ecto_query} -> - {:ok, - ecto_query - |> set_join_prefix(query, relationship.destination) - |> Ecto.Query.exclude(:select)} - - {:error, error} -> - {:error, error} - end - - %{errors: errors} -> - {:error, errors} - end - end - - defp limit_from_many(query, %{from_many?: true}) do - Ash.Query.limit(query, 1) - end - - defp limit_from_many(query, _), do: query - - defp set_has_parent_expr_context(query, relationship) do - has_parent_expr? = - Ash.Actions.Read.Relationships.has_parent_expr?(%{ - relationship - | filter: query.filter, - sort: query.sort - }) - - Ash.Query.set_context(query, %{data_layer: %{has_parent_expr?: has_parent_expr?}}) - end - - def set_join_prefix(join_query, query, resource) do - if Ash.Resource.Info.multitenancy_strategy(resource) == :context do - %{ - join_query - | prefix: query.prefix || AshPostgres.DataLayer.Info.schema(resource) || "public" - } - else - %{ - join_query - | prefix: - AshPostgres.DataLayer.Info.schema(resource) || - AshPostgres.DataLayer.Info.repo(resource, :mutate).config()[:default_prefix] || - "public" - } - end - end - - defp can_inner_join?(path, expr, seen_an_or? \\ false) - - defp can_inner_join?(path, %{expression: expr}, seen_an_or?), - do: can_inner_join?(path, expr, seen_an_or?) - - defp can_inner_join?(_path, expr, _seen_an_or?) when expr in [nil, true, false], do: true - - defp can_inner_join?(path, %BooleanExpression{op: :and, left: left, right: right}, seen_an_or?) do - can_inner_join?(path, left, seen_an_or?) || can_inner_join?(path, right, seen_an_or?) - end - - defp can_inner_join?(path, %BooleanExpression{op: :or, left: left, right: right}, _) do - can_inner_join?(path, left, true) && can_inner_join?(path, right, true) - end - - defp can_inner_join?( - _, - %Not{}, - _ - ) do - false - end - - defp can_inner_join?( - search_path, - %struct{__operator__?: true, left: %Ref{relationship_path: relationship_path}}, - seen_an_or? - ) - when search_path == relationship_path and struct in @known_inner_join_predicates do - not seen_an_or? - end - - defp can_inner_join?( - search_path, - %struct{__operator__?: true, right: %Ref{relationship_path: relationship_path}}, - seen_an_or? - ) - when search_path == relationship_path and struct in @known_inner_join_predicates do - not seen_an_or? - end - - defp can_inner_join?( - search_path, - %struct{__function__?: true, arguments: arguments}, - seen_an_or? - ) - when struct in @known_inner_join_predicates do - if Enum.any?(arguments, &match?(%Ref{relationship_path: ^search_path}, &1)) do - not seen_an_or? - else - true - end - end - - defp can_inner_join?(_, _, _), do: false - - @doc false - def get_binding(resource, candidate_path, %{__ash_bindings__: _} = query, types) do - types = List.wrap(types) - - Enum.find_value(query.__ash_bindings__.bindings, fn - {binding, %{path: path, source: source, type: type}} -> - if type in types && - Ash.SatSolver.synonymous_relationship_paths?(resource, path, candidate_path, source) do - binding - end - - _ -> - nil - end) - end - - def get_binding(_, _, _, _), do: nil - - defp add_distinct(relationship, _join_type, joined_query) do - if !joined_query.__ash_bindings__.in_group? && - relationship.cardinality == :many && - !joined_query.distinct do - from(row in joined_query, - distinct: ^Ash.Resource.Info.primary_key(joined_query.__ash_bindings__.resource) - ) - else - joined_query - end - end - - defp join_relationship( - query, - %{manual: {module, opts}} = relationship, - path, - kind, - source, - filter, - sort?, - apply_filter - ) do - full_path = path ++ [relationship.name] - initial_ash_bindings = query.__ash_bindings__ - - binding_data = %{type: kind, path: full_path, source: source} - - query = AshPostgres.DataLayer.add_binding(query, binding_data) - - used_aggregates = Ash.Filter.used_aggregates(filter, full_path) - - with {:ok, relationship_destination} <- - related_subquery(relationship, query, sort?: sort?) do - {relationship_destination, acc} = - maybe_apply_filter(relationship_destination, query, query.__ash_bindings__, apply_filter) - - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - binding_kinds = - case kind do - :left -> - [:left, :inner] - - :inner -> - [:left, :inner] - - other -> - [other] - end - - current_binding = - Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> - if data.type in binding_kinds && data.path == path do - binding - end - end) - - case module.ash_postgres_join( - query, - opts, - current_binding, - initial_ash_bindings.current, - kind, - relationship_destination - ) do - {:ok, query} -> - AshPostgres.Aggregate.add_aggregates( - query, - used_aggregates, - relationship.destination, - false, - initial_ash_bindings.current, - {query.__ash_bindings__.resource, full_path} - ) - - {:error, query} -> - {:error, query} - end - end - rescue - e in UndefinedFunctionError -> - if e.function == :ash_postgres_join do - reraise """ - Cannot join to a manual relationship #{inspect(module)} that does not implement the `AshPostgres.ManualRelationship` behaviour. - """, - __STACKTRACE__ - else - reraise e, __STACKTRACE__ - end - end - - defp join_relationship( - query, - %{type: :many_to_many} = relationship, - path, - kind, - source, - filter, - sort?, - apply_filter - ) do - join_relationship = - Ash.Resource.Info.relationship(relationship.source, relationship.join_relationship) - - join_path = path ++ [join_relationship.name] - - full_path = path ++ [relationship.name] - - initial_ash_bindings = query.__ash_bindings__ - - binding_data = %{type: kind, path: full_path, source: source} - - used_aggregates = Ash.Filter.used_aggregates(filter, full_path) - - query = - query - |> AshPostgres.DataLayer.add_binding(%{ - path: join_path, - type: :left, - source: source - }) - |> AshPostgres.DataLayer.add_binding(binding_data) - - with {:ok, relationship_through} <- related_subquery(join_relationship, query), - {:ok, relationship_destination} <- - related_subquery(relationship, query, sort?: sort?) do - {relationship_destination, dest_acc} = - maybe_apply_filter(relationship_destination, query, query.__ash_bindings__, apply_filter) - - query = - query - |> AshPostgres.DataLayer.merge_expr_accumulator(dest_acc) - - binding_kinds = - case kind do - :left -> - [:left, :inner] - - :inner -> - [:left, :inner] - - other -> - [other] - end - - current_binding = - Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> - if data.type in binding_kinds && data.path == path do - binding - end - end) - - query = - case kind do - :inner -> - from(_ in query, - join: through in ^relationship_through, - as: ^initial_ash_bindings.current, - on: - field(as(^current_binding), ^relationship.source_attribute) == - field(through, ^relationship.source_attribute_on_join_resource), - join: destination in ^relationship_destination, - as: ^(initial_ash_bindings.current + 1), - on: - field(destination, ^relationship.destination_attribute) == - field(through, ^relationship.destination_attribute_on_join_resource) - ) - - _ -> - from(_ in query, - left_join: through in ^relationship_through, - as: ^initial_ash_bindings.current, - on: - field(as(^current_binding), ^relationship.source_attribute) == - field(through, ^relationship.source_attribute_on_join_resource), - left_join: destination in ^relationship_destination, - as: ^(initial_ash_bindings.current + 1), - on: - field(destination, ^relationship.destination_attribute) == - field(through, ^relationship.destination_attribute_on_join_resource) - ) - end - - AshPostgres.Aggregate.add_aggregates( - query, - used_aggregates, - relationship.destination, - false, - initial_ash_bindings.current, - {query.__ash_bindings__.resource, full_path} - ) - end - end - - defp join_relationship( - query, - relationship, - path, - kind, - source, - filter, - sort?, - apply_filter - ) do - full_path = path ++ [relationship.name] - initial_ash_bindings = query.__ash_bindings__ - - binding_data = %{type: kind, path: full_path, source: source} - - query = AshPostgres.DataLayer.add_binding(query, binding_data) - - used_aggregates = Ash.Filter.used_aggregates(filter, full_path) - - binding_kinds = - case kind do - :left -> - [:left, :inner] - - :inner -> - [:left, :inner] - - other -> - [other] - end - - current_binding = - Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} -> - if data.type in binding_kinds && data.path == path do - binding - end - end) - - case related_subquery(relationship, query, - sort?: sort?, - on_parent_expr: fn subquery -> - if Map.get(relationship, :no_attributes?) do - subquery - else - from(row in subquery, - where: - field(parent_as(^current_binding), ^relationship.source_attribute) == - field( - row, - ^relationship.destination_attribute - ) - ) - end - end - ) do - {:error, error} -> - {:error, error} - - {:ok, relationship_destination} -> - {relationship_destination, acc} = - maybe_apply_filter( - relationship_destination, - query, - query.__ash_bindings__, - apply_filter - ) - - query = AshPostgres.DataLayer.merge_expr_accumulator(query, acc) - - query = - case {kind, Map.get(relationship, :no_attributes?, false), - relationship_destination.__ash_bindings__.context[:data_layer][ - :has_parent_expr? - ]} do - {:inner, true, false} -> - from(_ in query, - join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: true - ) - - {:inner, true, true} -> - from(_ in query, - inner_lateral_join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: true - ) - - {:inner, false, false} -> - from(_ in query, - join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: - field(as(^current_binding), ^relationship.source_attribute) == - field( - destination, - ^relationship.destination_attribute - ) - ) - - {:inner, false, true} -> - from(_ in query, - inner_lateral_join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: true - ) - - {:left, true, false} -> - from(_ in query, - left_join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: true - ) - - {:left, true, true} -> - from(_ in query, - left_lateral_join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: true - ) - - {:left, false, false} -> - from(_ in query, - left_join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: - field(as(^current_binding), ^relationship.source_attribute) == - field( - destination, - ^relationship.destination_attribute - ) - ) - - {:left, false, true} -> - from(_ in query, - left_lateral_join: destination in ^relationship_destination, - as: ^initial_ash_bindings.current, - on: true - ) - end - - AshPostgres.Aggregate.add_aggregates( - query, - used_aggregates, - relationship.destination, - false, - initial_ash_bindings.current, - {query.__ash_bindings__.resource, full_path} - ) - end - end - - @doc false - def maybe_apply_filter(query, _root_query, _bindings, nil), - do: {query, %AshPostgres.Expr.ExprInfo{}} - - def maybe_apply_filter(query, root_query, bindings, filter) do - {dynamic, acc} = AshPostgres.Expr.dynamic_expr(root_query, filter, bindings, true) - {from(row in query, where: ^dynamic), acc} - end -end diff --git a/lib/sort.ex b/lib/sort.ex deleted file mode 100644 index 4858949d..00000000 --- a/lib/sort.ex +++ /dev/null @@ -1,338 +0,0 @@ -defmodule AshPostgres.Sort do - @moduledoc false - require Ecto.Query - - def sort( - query, - sort, - resource, - relationship_path \\ [], - binding \\ 0, - type \\ :window - ) do - query = AshPostgres.DataLayer.default_bindings(query, resource) - - used_aggregates = - Enum.flat_map(sort, fn - {%Ash.Query.Calculation{} = calculation, _} -> - case Ash.Filter.hydrate_refs( - calculation.module.expression(calculation.opts, calculation.context), - %{ - resource: resource, - aggregates: %{}, - parent_stack: query.__ash_bindings__[:parent_resources] || [], - calculations: %{}, - public?: false - } - ) do - {:ok, hydrated} -> - Ash.Filter.used_aggregates(hydrated) - - _ -> - [] - end - - {key, _} -> - case Ash.Resource.Info.aggregate(resource, key) do - nil -> - [] - - aggregate -> - [aggregate] - end - - _ -> - [] - end) - - calcs = - Enum.flat_map(sort, fn - {%Ash.Query.Calculation{} = calculation, _} -> - {:ok, expression} = - calculation.opts - |> calculation.module.expression(calculation.context) - |> Ash.Filter.hydrate_refs(%{ - resource: resource, - parent_stack: query.__ash_bindings__[:parent_resources] || [], - public?: false - }) - - [{calculation, Ash.Filter.move_to_relationship_path(expression, relationship_path)}] - - _ -> - [] - end) - - {:ok, query} = - AshPostgres.Join.join_all_relationships( - query, - %Ash.Filter{ - resource: resource, - expression: Enum.map(calcs, &elem(&1, 1)) - }, - left_only?: true - ) - - case AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do - {:error, error} -> - {:error, error} - - {:ok, query} -> - sort - |> sanitize_sort() - |> Enum.reduce_while({:ok, [], query}, fn - {order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr, query} -> - type = - if calc.type do - AshPostgres.Types.parameterized_type(calc.type, calc.constraints) - else - nil - end - - calc.opts - |> calc.module.expression(calc.context) - |> Ash.Filter.hydrate_refs(%{ - resource: resource, - parent_stack: query.__ash_bindings__[:parent_resources] || [], - public?: false - }) - |> Ash.Filter.move_to_relationship_path(relationship_path) - |> case do - {:ok, expr} -> - bindings = - if query.__ash_bindings__[:parent_bindings] do - Map.update!(query.__ash_bindings__, :parent_bindings, fn parent -> - Map.put(parent, :parent_is_parent_as?, false) - end) - else - query.__ash_bindings__ - end - - {expr, acc} = - AshPostgres.Expr.dynamic_expr( - query, - expr, - bindings, - false, - type - ) - - {:cont, - {:ok, query_expr ++ [{order, expr}], - AshPostgres.DataLayer.merge_expr_accumulator(query, acc)}} - - {:error, error} -> - {:halt, {:error, error}} - end - - {order, sort}, {:ok, query_expr, query} -> - expr = - case find_aggregate_binding( - query.__ash_bindings__.bindings, - relationship_path, - sort - ) do - {:ok, binding} -> - aggregate = - Ash.Resource.Info.aggregate(resource, sort) || - raise "No such aggregate for query aggregate #{inspect(sort)}" - - {:ok, attribute_type} = - if aggregate.field do - related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - - attr = Ash.Resource.Info.attribute(related, aggregate.field) - - if attr && related do - {:ok, AshPostgres.Types.parameterized_type(attr.type, attr.constraints)} - else - {:ok, nil} - end - else - {:ok, nil} - end - - default_value = - aggregate.default || Ash.Query.Aggregate.default_value(aggregate.kind) - - if is_nil(default_value) do - Ecto.Query.dynamic(field(as(^binding), ^sort)) - else - if attribute_type do - Ecto.Query.dynamic( - coalesce( - field(as(^binding), ^sort), - type(^default_value, ^attribute_type) - ) - ) - else - Ecto.Query.dynamic(coalesce(field(as(^binding), ^sort), ^default_value)) - end - end - - :error -> - aggregate = Ash.Resource.Info.aggregate(resource, sort) - - {binding, sort} = - if aggregate && - AshPostgres.Aggregate.optimizable_first_aggregate?(resource, aggregate) do - {AshPostgres.Join.get_binding( - resource, - aggregate.relationship_path, - query, - [ - :left, - :inner - ] - ), aggregate.field} - else - {binding, sort} - end - - Ecto.Query.dynamic(field(as(^binding), ^sort)) - end - - {:cont, {:ok, query_expr ++ [{order, expr}], query}} - end) - |> case do - {:ok, [], query} -> - if type == :return do - {:ok, [], query} - else - {:ok, query} - end - - {:ok, sort_exprs, query} -> - case type do - :return -> - {:ok, order_to_fragments(sort_exprs), query} - - :window -> - new_query = Ecto.Query.order_by(query, ^sort_exprs) - - sort_expr = List.last(new_query.order_bys) - - new_query = - new_query - |> Map.update!(:windows, fn windows -> - order_by_expr = %{sort_expr | expr: [order_by: sort_expr.expr]} - Keyword.put(windows, :order, order_by_expr) - end) - |> Map.update!(:__ash_bindings__, &Map.put(&1, :__order__?, true)) - - {:ok, new_query} - - :direct -> - {:ok, query |> Ecto.Query.order_by(^sort_exprs) |> set_sort_applied()} - end - - {:error, error} -> - {:error, error} - end - end - end - - defp set_sort_applied(query) do - Map.update!(query, :__ash_bindings__, &Map.put(&1, :sort_applied?, true)) - end - - def find_aggregate_binding(bindings, relationship_path, sort) do - Enum.find_value( - bindings, - :error, - fn - {key, %{type: :aggregate, path: ^relationship_path, aggregates: aggregates}} -> - if Enum.any?(aggregates, &(&1.name == sort)) do - {:ok, key} - end - - _ -> - nil - end - ) - end - - def order_to_fragments([]), do: [] - - def order_to_fragments([last]) do - [do_order_to_fragments(last, false)] - end - - def order_to_fragments([first | rest]) do - [do_order_to_fragments(first, true) | order_to_fragments(rest)] - end - - def do_order_to_fragments({order, sort}, comma?) do - case {order, comma?} do - {:asc, false} -> - Ecto.Query.dynamic([row], fragment("? ASC", ^sort)) - - {:desc, false} -> - Ecto.Query.dynamic([row], fragment("? DESC", ^sort)) - - {:asc_nulls_last, false} -> - Ecto.Query.dynamic([row], fragment("? ASC NULLS LAST", ^sort)) - - {:asc_nulls_first, false} -> - Ecto.Query.dynamic([row], fragment("? ASC NULLS FIRST", ^sort)) - - {:desc_nulls_first, false} -> - Ecto.Query.dynamic([row], fragment("? DESC NULLS FIRST", ^sort)) - - {:desc_nulls_last, false} -> - Ecto.Query.dynamic([row], fragment("? DESC NULLS LAST", ^sort)) - "DESC NULLS LAST" - - {:asc, true} -> - Ecto.Query.dynamic([row], fragment("? ASC, ", ^sort)) - - {:desc, true} -> - Ecto.Query.dynamic([row], fragment("? DESC, ", ^sort)) - - {:asc_nulls_last, true} -> - Ecto.Query.dynamic([row], fragment("? ASC NULLS LAST, ", ^sort)) - - {:asc_nulls_first, true} -> - Ecto.Query.dynamic([row], fragment("? ASC NULLS FIRST, ", ^sort)) - - {:desc_nulls_first, true} -> - Ecto.Query.dynamic([row], fragment("? DESC NULLS FIRST, ", ^sort)) - - {:desc_nulls_last, true} -> - Ecto.Query.dynamic([row], fragment("? DESC NULLS LAST, ", ^sort)) - "DESC NULLS LAST" - end - end - - def order_to_postgres_order(dir) do - case dir do - :asc -> nil - :asc_nils_last -> " ASC NULLS LAST" - :asc_nils_first -> " ASC NULLS FIRST" - :desc -> " DESC" - :desc_nils_last -> " DESC NULLS LAST" - :desc_nils_first -> " DESC NULLS FIRST" - end - end - - defp sanitize_sort(sort) do - sort - |> List.wrap() - |> Enum.map(fn - {sort, {order, context}} -> - {ash_to_ecto_order(order), {sort, context}} - - {sort, order} -> - {ash_to_ecto_order(order), sort} - - sort -> - sort - end) - end - - defp ash_to_ecto_order(:asc_nils_last), do: :asc_nulls_last - defp ash_to_ecto_order(:asc_nils_first), do: :asc_nulls_first - defp ash_to_ecto_order(:desc_nils_last), do: :desc_nulls_last - defp ash_to_ecto_order(:desc_nils_first), do: :desc_nulls_first - defp ash_to_ecto_order(other), do: other -end diff --git a/lib/types/types.ex b/lib/sql_implementation.ex similarity index 67% rename from lib/types/types.ex rename to lib/sql_implementation.ex index ca0d9c39..842f13c2 100644 --- a/lib/types/types.ex +++ b/lib/sql_implementation.ex @@ -1,8 +1,120 @@ -defmodule AshPostgres.Types do +defmodule AshPostgres.SqlImplementation do @moduledoc false + use AshSql.Implementation - alias Ash.Query.Ref + require Ecto.Query + @impl true + def expr( + query, + %like{arguments: [arg1, arg2], embedded?: pred_embedded?}, + bindings, + embedded?, + acc, + type + ) + when like in [AshPostgres.Functions.Like, AshPostgres.Functions.ILike] do + {arg1, acc} = + AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc) + + {arg2, acc} = + AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc) + + inner_dyn = + if like == AshPostgres.Functions.Like do + Ecto.Query.dynamic(like(^arg1, ^arg2)) + else + Ecto.Query.dynamic(ilike(^arg1, ^arg2)) + end + + if type != Ash.Type.Boolean do + {:ok, inner_dyn, acc} + else + {:ok, Ecto.Query.dynamic(type(^inner_dyn, ^type)), acc} + end + end + + def expr( + query, + %AshPostgres.Functions.TrigramSimilarity{ + arguments: [arg1, arg2], + embedded?: pred_embedded? + }, + bindings, + embedded?, + acc, + _type + ) do + {arg1, acc} = + AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc) + + {arg2, acc} = + AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc) + + {:ok, Ecto.Query.dynamic(fragment("similarity(?, ?)", ^arg1, ^arg2)), acc} + end + + def expr( + query, + %AshPostgres.Functions.VectorCosineDistance{ + arguments: [arg1, arg2], + embedded?: pred_embedded? + }, + bindings, + embedded?, + acc, + _type + ) do + {arg1, acc} = + AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc) + + {arg2, acc} = + AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc) + + {:ok, Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)), acc} + end + + def expr( + _query, + _expr, + _bindings, + _embedded?, + _acc, + _type + ) do + :error + end + + @impl true + def table(resource) do + AshPostgres.DataLayer.Info.table(resource) + end + + @impl true + def schema(resource) do + AshPostgres.DataLayer.Info.schema(resource) + end + + @impl true + def repo(resource, kind) do + AshPostgres.DataLayer.Info.repo(resource, kind) + end + + @impl true + def simple_join_first_aggregates(resource) do + AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) + end + + @impl true + def list_aggregate(resource) do + if AshPostgres.DataLayer.Info.pg_version_matches?(resource, ">= 16.0.0") do + "any_value" + else + "array_agg" + end + end + + @impl true def parameterized_type(type, constraints, no_maps? \\ true) def parameterized_type({:parameterized, _, _} = type, _, _) do @@ -82,6 +194,7 @@ defmodule AshPostgres.Types do end end + @impl true def determine_types(mod, values) do Code.ensure_compiled(mod) @@ -198,7 +311,8 @@ defmodule AshPostgres.Types do defp fill_in_known_type( {{:array, type}, - %Ref{attribute: %{type: {:array, type}, constraints: constraints} = attribute} = ref} + %Ash.Query.Ref{attribute: %{type: {:array, type}, constraints: constraints} = attribute} = + ref} ) do {:in, fill_in_known_type( @@ -228,7 +342,7 @@ defmodule AshPostgres.Types do end defp fill_in_known_type( - {vague_type, %Ref{attribute: %{type: type, constraints: constraints}}} = ref + {vague_type, %Ash.Query.Ref{attribute: %{type: type, constraints: constraints}}} = ref ) when vague_type in [:any, :same] do if Ash.Type.ash_type?(type) do diff --git a/mix.exs b/mix.exs index febdaf25..351e970f 100644 --- a/mix.exs +++ b/mix.exs @@ -154,6 +154,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc.0")}, + {:ash_sql, "~> 0.1.1-rc.0"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 2a885f5f..f18fd1d4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.0", "5acbfff801258624320dad950b07ea20ac6d8fe06a197d96c806d0bc5567c1b1", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e0ff1ba71b7096480da0a1472b95de7b73b88971eeb78a20779ed2bbba532df8"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.0", "79c463fd59c864d39e96ec5a1cf9b509a8bb9b8a4c6a55a2a562bbc43159d2a8", [:mix], [{:ash, "~> 3.0.0-rc.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "014ebd36b8a56ddcd79f04958567abe173000bc4ca2ce169029d8b51d2c9267a"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -8,11 +9,9 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, @@ -26,9 +25,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 1d955159..ce6df24b 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.AggregateTest do +defmodule AshSql.AggregateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Author, Comment, Organization, Post, Rating, User} From eb08fed28bdf86f45ac701548456822d41f1623c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 1 Apr 2024 13:58:44 -0400 Subject: [PATCH 0333/1215] improvement: fixes for 3.0 changes and AshSql changes --- lib/data_layer.ex | 2 +- .../migration_generator.ex | 2 +- lib/sql_implementation.ex | 25 +++++++++++-------- lib/types/ci_string_wrapper.ex | 2 +- lib/types/string_wrapper.ex | 2 +- mix.exs | 4 +-- mix.lock | 10 ++++---- test/ash_postgres_test.exs | 14 +++++++---- test/calculation_test.exs | 8 +++--- test/error_expr_test.exs | 8 +++--- 10 files changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e7165cd9..6cff16a1 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -546,7 +546,7 @@ defmodule AshPostgres.DataLayer do @impl true def set_context(resource, data_layer_query, context) do - AshSql.Query.set_context(resource, data_layer_query, context) + AshSql.Query.set_context(resource, data_layer_query, AshPostgres.SqlImplementation, context) end @impl true diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7d67f989..e8d033ca 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2520,7 +2520,7 @@ defmodule AshPostgres.MigrationGenerator do defp has_create_action?(resource) do resource |> Ash.Resource.Info.actions() - |> Enum.any?(&(&1.type == :create)) + |> Enum.any?(&(&1.type == :create && !&1.manual)) end defp check_constraints(resource) do diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 842f13c2..bd43e470 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -4,6 +4,18 @@ defmodule AshPostgres.SqlImplementation do require Ecto.Query + @impl true + def manual_relationship_function, do: :ash_postgres_join + + @impl true + def manual_relationship_subquery_function, do: :ash_postgres_subquery + + @impl true + def require_ash_functions_for_or_and_and?, do: true + + @impl true + def require_extension_for_citext, do: {true, "citext"} + @impl true def expr( query, @@ -136,11 +148,11 @@ defmodule AshPostgres.SqlImplementation do end def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do - parameterized_type(Ash.Type.CiStringWrapper, constraints, no_maps?) + parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) end - def parameterized_type(Ash.Type.String.EctoType, constraints, no_maps?) do - parameterized_type(Ash.Type.StringWrapper, constraints, no_maps?) + def parameterized_type(Ash.Type.String, constraints, no_maps?) do + parameterized_type(AshPostgres.Type.StringWrapper, constraints, no_maps?) end def parameterized_type(:tsquery, constraints, no_maps?) do @@ -167,13 +179,6 @@ defmodule AshPostgres.SqlImplementation do if cast_in_query? do type = Ash.Type.ecto_type(type) - type = - if type.type(constraints) == :ci_string do - Ash.Type.CiStringWrapper - else - type - end - parameterized_type(type, constraints, no_maps?) else nil diff --git a/lib/types/ci_string_wrapper.ex b/lib/types/ci_string_wrapper.ex index 72f850d4..20a9b12e 100644 --- a/lib/types/ci_string_wrapper.ex +++ b/lib/types/ci_string_wrapper.ex @@ -1,4 +1,4 @@ -defmodule Ash.Type.CiStringWrapper do +defmodule AshPostgres.Type.CiStringWrapper do @moduledoc false use Ash.Type diff --git a/lib/types/string_wrapper.ex b/lib/types/string_wrapper.ex index 82d067da..e6101c52 100644 --- a/lib/types/string_wrapper.ex +++ b/lib/types/string_wrapper.ex @@ -1,4 +1,4 @@ -defmodule Ash.Type.StringWrapper do +defmodule AshPostgres.Type.StringWrapper do @moduledoc false use Ash.Type diff --git a/mix.exs b/mix.exs index 351e970f..2501ed64 100644 --- a/mix.exs +++ b/mix.exs @@ -154,7 +154,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc.0")}, - {:ash_sql, "~> 0.1.1-rc.0"}, + {:ash_sql, "~> 0.1.1-rc.3"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, @@ -178,7 +178,7 @@ defmodule AshPostgres.MixProject do default_version "local" -> - [path: "../ash"] + [path: "../ash", override: true] "main" -> [git: "https://github.com/ash-project/ash.git"] diff --git a/mix.lock b/mix.lock index f18fd1d4..74aed06f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.0", "5acbfff801258624320dad950b07ea20ac6d8fe06a197d96c806d0bc5567c1b1", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e0ff1ba71b7096480da0a1472b95de7b73b88971eeb78a20779ed2bbba532df8"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.0", "79c463fd59c864d39e96ec5a1cf9b509a8bb9b8a4c6a55a2a562bbc43159d2a8", [:mix], [{:ash, "~> 3.0.0-rc.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "014ebd36b8a56ddcd79f04958567abe173000bc4ca2ce169029d8b51d2c9267a"}, + "ash": {:hex, :ash, "3.0.0-rc.6", "78d9bc068a0c632e4fe2db8a8802f772c65329c8bc15877ceb6eb2ac83e1fa8b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3e0ccc857572d10972868886aff46f9b1d11c90f8b357f85f2887e71f702e916"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.3", "4d0043b6560a90da70f9d1950968113b2fcb3e0e036a2e4112168ea3786c8429", [:mix], [{:ash, "~> 3.0.0-rc.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b6f32c1d3ec86983481708583be9d6b0959adbc1cb2de8987c816eb238b97762"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -26,13 +26,13 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.8", "406256443d5e23ec034a0520c5bee703385ce0840825194aa583c96c22c2a349", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "cc46f7b3d31efe7995d6348e1664b1e18d5193761ad5462d61059078578d5f4c"}, - "splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"}, + "spark": {:hex, :spark, "2.1.11", "8093149dfd583b5ce2c06e1fea1faaf4125b50e4703138b2cbefb78c8f4aa07f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1877d92ab993b860e9d828bfd72d50367c0d3a53dd84f4de5d221baf66ae8723"}, + "splode": {:hex, :splode, "0.2.1", "020079ec06c9e00f8b6586852e781b5e07aee6ba588f3f45dd993831c87b0511", [:mix], [], "hexpm", "d232a933666061fe1f659d9906042fa94b9b393bb1129a4fde6fa680033b2611"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index dfcf765d..921f50d7 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -22,15 +22,19 @@ defmodule AshPostgresTest do post |> Ash.Changeset.for_update(:update, %{title: "bad"}, authorize?: true, + actor: nil, actor: %{id: Ash.UUID.generate()} ) - |> Ash.update!() + |> Ash.update!( + authorize?: true, + actor: nil + ) |> Map.get(:title) end - post - |> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true) - |> Ash.update!() - |> Map.get(:title) + # post + # |> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true) + # |> Ash.update!() + # |> Map.get(:title) end end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index c29224cf..ff46dc52 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -692,8 +692,8 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.calculate( :length, - expr(string_length(string_trim(first_name <> last_name <> " "))), - :integer + :integer, + expr(string_length(string_trim(first_name <> last_name <> " "))) ) |> Ash.read_one!() end @@ -726,8 +726,8 @@ defmodule AshPostgres.CalculationTest do Author |> Ash.Query.calculate( :string, - expr(lazy({__MODULE__, :fred, []})), - :string + :string, + expr(lazy({__MODULE__, :fred, []})) ) |> Ash.read_one!() end diff --git a/test/error_expr_test.exs b/test/error_expr_test.exs index a45c1a49..d73577ad 100644 --- a/test/error_expr_test.exs +++ b/test/error_expr_test.exs @@ -28,8 +28,8 @@ defmodule AshPostgres.ErrorExprTest do Post |> Ash.Query.calculate( :test, - expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), - :string + :string, + expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)) ) |> Ash.read!() |> Enum.map(& &1.calculations) @@ -46,8 +46,8 @@ defmodule AshPostgres.ErrorExprTest do Post |> Ash.Query.calculate( :test, - expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), - :string + :string, + expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)) ) |> Ash.read!() |> Enum.map(& &1.calculations) From e6fbda3a1227bc22ad636ff8586af37c3b36558a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 1 Apr 2024 13:59:29 -0400 Subject: [PATCH 0334/1215] chore: release version v2.0.0-rc.3 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8556742d..0ca4a926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2024-04-01) + + + + +### Improvements: + +* fixes for 3.0 changes and AshSql changes + +* move many internals out to `AshSql` package + ## [v2.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.1...v2.0.0-rc.2) (2024-03-29) ### Breaking Changes: diff --git a/mix.exs b/mix.exs index 2501ed64..831ef35c 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.2" + @version "2.0.0-rc.3" def project do [ From 4db00965575f21b470573a3b7173a954dd88316a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Apr 2024 12:28:06 -0400 Subject: [PATCH 0335/1215] improvement: loosen 3.0 release candidate requirement --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 831ef35c..d18d0daa 100644 --- a/mix.exs +++ b/mix.exs @@ -153,7 +153,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0.0-rc.0")}, + {:ash, ash_version("~> 3.0.0-rc")}, {:ash_sql, "~> 0.1.1-rc.3"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, From 62ffc66d3c273580b3ab96bab4244917b44eb443 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Apr 2024 12:28:21 -0400 Subject: [PATCH 0336/1215] chore: release version v2.0.0-rc.4 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca4a926..fa50dc6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.3...v2.0.0-rc.4) (2024-04-02) + + + + +### Improvements: + +* loosen 3.0 release candidate requirement + ## [v2.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2024-04-01) diff --git a/mix.exs b/mix.exs index d18d0daa..258d7870 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.3" + @version "2.0.0-rc.4" def project do [ From 7d83cecad7f03eeb58430c0a11d39bdd35e2b9f8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Apr 2024 23:03:57 -0400 Subject: [PATCH 0337/1215] fix: don't wait for shell input when checking migrations --- .../migration_generator.ex | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index e8d033ca..392346c6 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -701,8 +701,8 @@ defmodule AshPostgres.MigrationGenerator do #{unique_primary_key_names} """ - message - |> Mix.shell().prompt() + opts + |> prompt(message) |> String.to_integer() end @@ -760,6 +760,22 @@ defmodule AshPostgres.MigrationGenerator do end end + defp yes?(opts, message) do + if opts.check do + true + else + Mix.shell().yes?(message) + end + end + + defp prompt(opts, message) do + if opts.check do + "response" + else + Mix.shell().prompt(message) + end + end + defp pkey_names(attributes) do attributes |> Enum.filter(& &1.primary_key?) @@ -2357,7 +2373,7 @@ defmodule AshPostgres.MigrationGenerator do if opts.no_shell? do raise "Unimplemented: Cannot get new_attribute without the shell!" else - get_new_attribute(adding) + get_new_attribute(adding, opts) end {Enum.reject(adding, &(&1.source == new_attribute.source)), [], @@ -2375,7 +2391,7 @@ defmodule AshPostgres.MigrationGenerator do if opts.no_shell? do raise "Unimplemented: cannot determine: Are you renaming #{table}.#{removing} to #{table}.#{adding}? without shell input" else - Mix.shell().yes?("Are you renaming #{table}.#{removing} to #{table}.#{adding}?") + yes?(opts, "Are you renaming #{table}.#{removing} to #{table}.#{adding}?") end end @@ -2383,19 +2399,20 @@ defmodule AshPostgres.MigrationGenerator do if opts.no_shell? do raise "Unimplemented: cannot determine: Are you renaming #{table}.#{removing.source}? without shell input" else - Mix.shell().yes?("Are you renaming #{table}.#{removing.source}?") + yes?(opts, "Are you renaming #{table}.#{removing.source}?") end end - defp get_new_attribute(adding, tries \\ 3) + defp get_new_attribute(adding, opts, tries \\ 3) - defp get_new_attribute(_adding, 0) do + defp get_new_attribute(_adding, _opts, 0) do raise "Could not get matching name after 3 attempts." end - defp get_new_attribute(adding, tries) do + defp get_new_attribute(adding, opts, tries) do name = - Mix.shell().prompt( + prompt( + opts, "What are you renaming it to?: #{Enum.map_join(adding, ", ", & &1.source)}" ) @@ -2407,7 +2424,7 @@ defmodule AshPostgres.MigrationGenerator do end case Enum.find(adding, &(to_string(&1.source) == name)) do - nil -> get_new_attribute(adding, tries - 1) + nil -> get_new_attribute(adding, opts, tries - 1) new_attribute -> new_attribute end end From 5c877137dc281bff4c15572092c78db3085d26fe Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 5 Apr 2024 14:46:38 -0400 Subject: [PATCH 0338/1215] improvement: don't fetch version in agent when using sandbox --- lib/repo.ex | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/repo.ex b/lib/repo.ex index 2b97dcf2..408ab54b 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -214,14 +214,37 @@ defmodule AshPostgres.Repo do end defp cached_version do - Agent.start_link( - fn -> - lookup_version() - end, - name: @agent - ) - - Agent.get(@agent, fn state -> state end) + if config()[:pool] == Ecto.Adapters.SQL.Sandbox do + Agent.start_link( + fn -> + nil + end, + name: @agent + ) + + case Agent.get(@agent, fn state -> state end) do + nil -> + version = lookup_version() + + Agent.update(@agent, fn _ -> + version + end) + + version + + version -> + version + end + else + Agent.start_link( + fn -> + lookup_version() + end, + name: @agent + ) + + Agent.get(@agent, fn state -> state end) + end end defp lookup_version do From ac7afc0500542387a1283ecb9a6bc96cb4afb01c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 5 Apr 2024 14:46:58 -0400 Subject: [PATCH 0339/1215] chore: release version v2.0.0-rc.5 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa50dc6d..5dda726d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.4...v2.0.0-rc.5) (2024-04-05) + + + + +### Bug Fixes: + +* don't wait for shell input when checking migrations + +### Improvements: + +* don't fetch version in agent when using sandbox + ## [v2.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.3...v2.0.0-rc.4) (2024-04-02) diff --git a/mix.exs b/mix.exs index 258d7870..de44897c 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.4" + @version "2.0.0-rc.5" def project do [ From 09c20837204e7f2e47b466e5dacfdb47c5cd9cce Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 5 Apr 2024 19:27:43 -0400 Subject: [PATCH 0340/1215] chore: update ash_sql --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 74aed06f..03a12944 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.6", "78d9bc068a0c632e4fe2db8a8802f772c65329c8bc15877ceb6eb2ac83e1fa8b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3e0ccc857572d10972868886aff46f9b1d11c90f8b357f85f2887e71f702e916"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.3", "4d0043b6560a90da70f9d1950968113b2fcb3e0e036a2e4112168ea3786c8429", [:mix], [{:ash, "~> 3.0.0-rc.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b6f32c1d3ec86983481708583be9d6b0959adbc1cb2de8987c816eb238b97762"}, + "ash": {:hex, :ash, "3.0.0-rc.14", "c7c9441e5ff51f6c67ba4cb642689c7c7bbeedaa3d488d40cc03a1a9b23134e4", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a252d6d6518a8cc3aa893b57bb484a254ed3248c8d81a3c66e7ce0f4b68c7a1"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.4", "74ef18e2057621e48a8930d2b765219eabf9424382e571dd2566009fe2ee76ab", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "329065f9d119f55444908933cfb9e4a47ea82bfd23ac46c040d517f669d9d385"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -14,7 +14,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, - "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, + "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, "excoveralls": {:hex, :excoveralls, "0.18.0", "b92497e69465dc51bc37a6422226ee690ab437e4c06877e836f1c18daeb35da9", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1109bb911f3cb583401760be49c02cbbd16aed66ea9509fc5479335d284da60b"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, @@ -28,10 +28,10 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, - "simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"}, + "simple_sat": {:hex, :simple_sat, "0.1.2", "1e6eb889929de39bad81e72914bba0ab7a7f65dd1bed206575e34a2365f3187d", [:mix], [], "hexpm", "e476182200329d55075477375d705a81186cdad4f70590d6b94765a8c154d2c0"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.11", "8093149dfd583b5ce2c06e1fea1faaf4125b50e4703138b2cbefb78c8f4aa07f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1877d92ab993b860e9d828bfd72d50367c0d3a53dd84f4de5d221baf66ae8723"}, + "spark": {:hex, :spark, "2.1.13", "5c002181eed1c4336942267da20f4614b35c40aa347d35183190a7496196f802", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "2d5580313bbf6717d650a27554a66c83e10d164e7087e3c4082cdb23b5dc5c64"}, "splode": {:hex, :splode, "0.2.1", "020079ec06c9e00f8b6586852e781b5e07aee6ba588f3f45dd993831c87b0511", [:mix], [], "hexpm", "d232a933666061fe1f659d9906042fa94b9b393bb1129a4fde6fa680033b2611"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From cfaf1f82014c4b69d8ab9d2e9fa14c11af067564 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 6 Apr 2024 10:12:31 -0400 Subject: [PATCH 0341/1215] test: fix test application in CI fix: use proper sql implementation in `default_bindings` --- lib/data_layer.ex | 4 ++-- mix.exs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6cff16a1..b05cf5a6 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1211,14 +1211,14 @@ defmodule AshPostgres.DataLayer do nil -> {:ok, query - |> AshSql.Bindings.default_bindings(resource, AshSql.Implementation, context) + |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation, context) |> Ecto.Query.exclude(:select) |> Ecto.Query.exclude(:order_by)} %{qual: :inner} -> {:ok, query - |> AshSql.Bindings.default_bindings(resource, AshSql.Implementation, context) + |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation, context) |> Ecto.Query.exclude(:select) |> Ecto.Query.exclude(:order_by)} diff --git a/mix.exs b/mix.exs index de44897c..aec1439e 100644 --- a/mix.exs +++ b/mix.exs @@ -45,7 +45,7 @@ defmodule AshPostgres.MixProject do if Mix.env() == :test do def application() do [ - applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee, :xmerl], + applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee, :xmerl, :ash_sql], mod: {AshPostgres.TestApp, []} ] end @@ -154,7 +154,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc")}, - {:ash_sql, "~> 0.1.1-rc.3"}, + {:ash_sql, "~> 0.1.1-rc"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, From a02653e276422141f5aec9b149838e18548f9ba4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 6 Apr 2024 15:09:54 -0400 Subject: [PATCH 0342/1215] chore: use `extra_applications` instead of `applications` --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index aec1439e..638a3b2b 100644 --- a/mix.exs +++ b/mix.exs @@ -45,7 +45,7 @@ defmodule AshPostgres.MixProject do if Mix.env() == :test do def application() do [ - applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee, :xmerl, :ash_sql], + extra_applications: [:tools, :xmerl], mod: {AshPostgres.TestApp, []} ] end From 38eec0ba86ed1ce88087bc808ea8a5ddb0783e94 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Apr 2024 20:21:04 -0400 Subject: [PATCH 0343/1215] docs: reformat docs and revisit certain guides improvement: support `mix ash.rollback` with interactive rollback --- config/config.exs | 2 + .../dsls/DSL:-AshPostgres.DataLayer.md | 2 +- documentation/home.md | 32 ++++++ documentation/how_to/using-fragments.md | 43 -------- documentation/topics/advanced/expressions.md | 77 +++++++++++++ .../advanced/manual-relationships.md} | 2 +- .../schema-based-multitenancy.md | 0 .../migrations-and-tasks.md} | 0 .../development/testing.md} | 2 +- .../development/upgrading-to-2.0.md} | 2 +- documentation/topics/postgres-expressions.md | 40 ------- .../polymorphic-resources.md} | 0 .../topics/{ => resources}/references.md | 10 +- lib/data_layer.ex | 101 +++++++++++++++++- lib/mix/helpers.ex | 2 +- lib/mix/tasks/ash_postgres.create.ex | 4 +- lib/mix/tasks/ash_postgres.drop.ex | 4 +- .../tasks/ash_postgres.generate_migrations.ex | 2 +- lib/mix/tasks/ash_postgres.migrate.ex | 16 +-- lib/mix/tasks/ash_postgres.rollback.ex | 16 +-- mix.exs | 47 +++++--- .../20240229050455_install_5_extensions.exs | 4 +- 22 files changed, 277 insertions(+), 131 deletions(-) create mode 100644 documentation/home.md delete mode 100644 documentation/how_to/using-fragments.md create mode 100644 documentation/topics/advanced/expressions.md rename documentation/{how_to/join-manual-relationships.md => topics/advanced/manual-relationships.md} (99%) rename documentation/topics/{ => advanced}/schema-based-multitenancy.md (100%) rename documentation/topics/{migrations_and_tasks.md => development/migrations-and-tasks.md} (100%) rename documentation/{how_to/test-with-postgres.md => topics/development/testing.md} (97%) rename documentation/{how_to/upgrade.md => topics/development/upgrading-to-2.0.md} (98%) delete mode 100644 documentation/topics/postgres-expressions.md rename documentation/topics/{polymorphic_resources.md => resources/polymorphic-resources.md} (100%) rename documentation/topics/{ => resources}/references.md (65%) diff --git a/config/config.exs b/config/config.exs index f3c392c6..10ce748c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -18,6 +18,8 @@ if Mix.env() == :test do config :ash, :validate_domain_resource_inclusion?, false config :ash, :validate_domain_config_inclusion?, false + config :ash_postgres, :ash_domains, [AshPostgres.Test.Domain] + config :ash_postgres, AshPostgres.TestRepo, username: "postgres", database: "ash_postgres_test", diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index b70c3365..8dd07ecf 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -52,7 +52,7 @@ end | [`migration_ignore_attributes`](#postgres-migration_ignore_attributes){: #postgres-migration_ignore_attributes } | `list(atom)` | `[]` | A list of attributes that will be ignored when generating migrations. | | [`table`](#postgres-table){: #postgres-table } | `String.t` | | The table to store and read the resource from. If this is changed, the migration generator will not remove the old table. | | [`schema`](#postgres-schema){: #postgres-schema } | `String.t` | | The schema that the table is located in. Schema-based multitenancy will supercede this option. If this is changed, the migration generator will not remove the old schema. | -| [`polymorphic?`](#postgres-polymorphic?){: #postgres-polymorphic? } | `boolean` | `false` | Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. | +| [`polymorphic?`](#postgres-polymorphic?){: #postgres-polymorphic? } | `boolean` | `false` | Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/resources/polymorphic-resources.md) for more. | ## postgres.custom_indexes diff --git a/documentation/home.md b/documentation/home.md new file mode 100644 index 00000000..40d1f66e --- /dev/null +++ b/documentation/home.md @@ -0,0 +1,32 @@ +# AshPostgres Documentation + +Welcome! This documentation is for `AshPostgres`, the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). If you have not yet, please see the [Ash Framework documentation](https://hexdocs.pm/ash). + +## Dive In + +- [Get Started](documentation/tutorials/get-started-with-postgres.md) + +--- + +## Tutorials + +- [Get Started](documentation/tutorials/get-started-with-postgres.md) + +## Topics + +### Resources + +- [References](documentation/topics/resources/references.md) +- [Polymorphic Resources](documentation/topics/resources/polymorphic-resources.md) + +### Development + +- [Migrations and tasks](documentation/topics/development/migrations-and-tasks.md) +- [Testing](documentation/topics/development/testing.md) +- [Upgrading to 2.0](documentation/topics/development/upgrading-to-2.0.md) + +### Advanced + +- [Expressions](documentation/topics/advanced/expressions.md) +- [Manual Relationships](documentation/topics/advanced/manual-relationships.md) +- [Schema Based Multitenancy](documentation/topics/advanced/schema-based-multitenancy.md) diff --git a/documentation/how_to/using-fragments.md b/documentation/how_to/using-fragments.md deleted file mode 100644 index 5029a91a..00000000 --- a/documentation/how_to/using-fragments.md +++ /dev/null @@ -1,43 +0,0 @@ -# Using Fragments - -Fragments allow you to use arbitrary postgres expressions in your queries. Fragments can often be an escape hatch to allow you to do things that don't have something officially supported with Ash. - -## Examples - -Use simple expressions - -```elixir -fragment("? / ?", points, count) -``` - -Call functions - -```elixir -fragment("repeat('hello', 4)") -``` - -Use entire queries - -```elixir -fragment("points > (SELECT SUM(points) FROM games WHERE user_id = ? AND id != ?)", user_id, id) -``` - -Using entire queries like the above is a last resort, but can often help us avoid having to add extra structure unnecessarily. - -sql function in a calculate - -```elixir -calculations do - calculate :lower_name, :string, expr( - fragment("LOWER(?)", name) - ) -end -``` - -sql function in a migration - -```elixir -create table(:managers, primary_key: false) do - add :id, :uuid, null: false, default: fragment("UUID_GENERATE_V4()"), primary_key: true -end -``` diff --git a/documentation/topics/advanced/expressions.md b/documentation/topics/advanced/expressions.md new file mode 100644 index 00000000..62d4f3f8 --- /dev/null +++ b/documentation/topics/advanced/expressions.md @@ -0,0 +1,77 @@ +# Expressions + +In addition to the expressions listed in the [Ash expressions guide](https://hexdocs.pm/ash/expressions.html), AshPostgres provides the following expressions + +# Fragments + +Fragments allow you to use arbitrary postgres expressions in your queries. Fragments can often be an escape hatch to allow you to do things that don't have something officially supported with Ash. + +### Examples + +#### Simple expressions + +```elixir +fragment("? / ?", points, count) +``` + +#### Calling functions + +```elixir +fragment("repeat('hello', 4)") +``` + +#### Using entire queries + +```elixir +fragment("points > (SELECT SUM(points) FROM games WHERE user_id = ? AND id != ?)", user_id, id) +``` + +> ### a last resport {: .warning} +> +> Using entire queries as shown above is a last resort, but can sometimes be the best way to accomplish a given task. + +#### In calculations + +```elixir +calculations do + calculate :lower_name, :string, expr( + fragment("LOWER(?)", name) + ) +end +``` + +#### In migrations + +```elixir +create table(:managers, primary_key: false) do + add :id, :uuid, null: false, default: fragment("UUID_GENERATE_V4()"), primary_key: true +end +``` + +## Like and ILike + +These wrap the postgres builtin like and ilike operators. + +Please be aware, these match *patterns* not raw text. Use `contains/1` if you want to match text without supporting patterns, i.e `%` and `_` have semantic meaning! + +For example: + +```elixir +Ash.Query.filter(User, like(name, "%obo%")) # name contains obo anywhere in the string, case sensitively +``` + +```elixir +Ash.Query.filter(User, ilike(name, "%ObO%")) # name contains ObO anywhere in the string, case insensitively +``` + +## Trigram similarity + +To use this expression, you must have the `pg_trgm` extension in your repos `installed_extensions` list. + +This calls the `similarity` function from that extension. See more https://www.postgresql.org/docs/current/pgtrgm.htmlhere: https://www.postgresql.org/docs/current/pgtrgm.html + +For example: + +```elixir +Ash.Query.filter(User, trigram_similarity(first_name, "fred") > 0.8) +``` diff --git a/documentation/how_to/join-manual-relationships.md b/documentation/topics/advanced/manual-relationships.md similarity index 99% rename from documentation/how_to/join-manual-relationships.md rename to documentation/topics/advanced/manual-relationships.md index 76b577de..ef261c5d 100644 --- a/documentation/how_to/join-manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -1,4 +1,4 @@ -# Join Manual Relationships +# Manual Relationships See [Defining Manual Relationships](https://hexdocs.pm/ash/defining-manual-relationships.html) for an idea of manual relationships in general. Manual relationships allow for expressing complex/non-typical relationships between resources in a standard way. diff --git a/documentation/topics/schema-based-multitenancy.md b/documentation/topics/advanced/schema-based-multitenancy.md similarity index 100% rename from documentation/topics/schema-based-multitenancy.md rename to documentation/topics/advanced/schema-based-multitenancy.md diff --git a/documentation/topics/migrations_and_tasks.md b/documentation/topics/development/migrations-and-tasks.md similarity index 100% rename from documentation/topics/migrations_and_tasks.md rename to documentation/topics/development/migrations-and-tasks.md diff --git a/documentation/how_to/test-with-postgres.md b/documentation/topics/development/testing.md similarity index 97% rename from documentation/how_to/test-with-postgres.md rename to documentation/topics/development/testing.md index 249202b0..64052f97 100644 --- a/documentation/how_to/test-with-postgres.md +++ b/documentation/topics/development/testing.md @@ -1,4 +1,4 @@ -# Testing With Postgres +# Testinging with AshPostgres When using AshPostgres resources in tests, you will likely want to include use a test case similar to the following. This will ensure that your repo runs everything in a transaction. diff --git a/documentation/how_to/upgrade.md b/documentation/topics/development/upgrading-to-2.0.md similarity index 98% rename from documentation/how_to/upgrade.md rename to documentation/topics/development/upgrading-to-2.0.md index 3c65b03f..34eeb78c 100644 --- a/documentation/how_to/upgrade.md +++ b/documentation/topics/development/upgrading-to-2.0.md @@ -1,4 +1,4 @@ -# Upgrade from 1.0 to 2.0 +# Upgrading to 2.0 There are only three breaking changes in this release, one of them is very significant, the other two are minor. diff --git a/documentation/topics/postgres-expressions.md b/documentation/topics/postgres-expressions.md deleted file mode 100644 index 88e0a54b..00000000 --- a/documentation/topics/postgres-expressions.md +++ /dev/null @@ -1,40 +0,0 @@ -# Postgres Expressions - -In addition to the expressions listed in the [Ash expressions guide](https://hexdocs.pm/ash/expressions.html), AshPostgres provides the following expressions - -## Fragments -`fragment` allows you to embed raw sql into the query. Use question marks to interpolate values from the outer expression. - -For example: - -```elixir -Ash.Query.filter(User, fragment("? IS NOT NULL", first_name)) -``` - -# Like and ILike - -These wrap the postgres builtin like and ilike operators. - -Please be aware, these match *patterns* not raw text. Use `contains/1` if you want to match text without supporting patterns, i.e `%` and `_` have semantic meaning! - -For example: - -```elixir -Ash.Query.filter(User, like(name, "%obo%")) # name contains obo anywhere in the string, case sensitively -``` - -```elixir -Ash.Query.filter(User, ilike(name, "%ObO%")) # name contains ObO anywhere in the string, case insensitively -``` - -# Trigram similarity - -To use this expression, you must have the `pg_trgm` extension in your repos `installed_extensions` list. - -This calls the `similarity` function from that extension. See more https://www.postgresql.org/docs/current/pgtrgm.htmlhere: https://www.postgresql.org/docs/current/pgtrgm.html - -For example: - -```elixir -Ash.Query.filter(User, trigram_similarity(first_name, "fred") > 0.8) -``` \ No newline at end of file diff --git a/documentation/topics/polymorphic_resources.md b/documentation/topics/resources/polymorphic-resources.md similarity index 100% rename from documentation/topics/polymorphic_resources.md rename to documentation/topics/resources/polymorphic-resources.md diff --git a/documentation/topics/references.md b/documentation/topics/resources/references.md similarity index 65% rename from documentation/topics/references.md rename to documentation/topics/resources/references.md index ceecb612..fd7860b9 100644 --- a/documentation/topics/references.md +++ b/documentation/topics/resources/references.md @@ -1,6 +1,6 @@ # References -To configure the foreign keys on a resource, we use the `references` block. +To configure the behavior of generated foreign keys on a resource, we use the `references` section. For example: @@ -10,9 +10,9 @@ references do end ``` -## Important - -No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens *directly* in the database. That +> ### Actions are not used for this behavior {: .warning} +> +> No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens *directly* in the database. ## Nothing vs Restrict @@ -20,4 +20,4 @@ The difference between `:nothing` and `:restrict` is subtle and, if you are unsu ## On Delete -This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, *not* a `destroy` action in your resource. +This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, *not* a `destroy` action in your resource. See the warning above. diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b05cf5a6..bf4f1f33 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -365,7 +365,7 @@ defmodule AshPostgres.DataLayer do type: :boolean, default: false, doc: """ - Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/polymorphic_resources.md) for more. + Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/resources/polymorphic-resources.md) for more. """ ] ] @@ -389,10 +389,107 @@ defmodule AshPostgres.DataLayer do ] def migrate(args) do - # TODO: take args that we care about Mix.Task.run("ash_postgres.migrate", args) end + def rollback(args) do + repos = AshPostgres.Mix.Helpers.repos!([], args) + + show_for_repo? = Enum.count_until(repos, 2) == 2 + + for repo <- repos do + for_repo = + if show_for_repo? do + " for repo #{inspect(repo)}" + else + "" + end + + migrations_path = AshPostgres.Mix.Helpers.migrations_path([], repo) + tenant_migrations_path = AshPostgres.Mix.Helpers.tenant_migrations_path([], repo) + + files = + migrations_path + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.sort() + |> Enum.reverse() + |> Enum.take(20) + |> Enum.map(&String.trim_leading(&1, migrations_path)) + |> Enum.with_index() + |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) + + n = + Mix.shell().prompt(""" + How many migrations should be rolled back#{for_repo}? (default: 0) + + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: + + #{Enum.join(files, "\n")} + Rollback to: + """ |> String.trim_trailing()) + |> String.trim() + |> case do + "" -> + 0 + + n -> + try do + String.to_integer(n) + rescue + _ -> + raise "Required an integer value, got: #{n}" + end + end + + Mix.Task.run("ash_postgres.rollback", args ++ ["-r", inspect(repo), "-n", to_string(n)]) + Mix.Task.reenable("ash_postgres.rollback") + + tenant_files = + tenant_migrations_path + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.sort() + |> Enum.reverse() + |> Enum.take(20) + |> Enum.map(&String.trim_leading(&1, tenant_migrations_path)) + |> Enum.with_index() + |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) + + if !Enum.empty?(tenant_files) do + n = + Mix.shell().prompt(""" + + How many _tenant_ migrations should be rolled back#{for_repo}? (default: 0) + + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: + + #{Enum.join(tenant_files, "\n")} + + Rollback to: + """) + |> String.trim() + |> case do + "" -> + 0 + + n -> + try do + String.to_integer(n) + rescue + _ -> + raise "Required an integer value, got: #{n}" + end + end + + Mix.Task.run("ash_postgres.rollback", args ++ ["--tenants", "-r", inspect(repo), "-n", to_string(n)]) + Mix.Task.reenable("ash_postgres.rollback") + end + end + end + def codegen(args) do {args, _, _} = OptionParser.parse(args, strict: [name: :string]) diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 2a4ea757..685b7e3d 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -1,4 +1,4 @@ -defmodule AshPostgres.MixHelpers do +defmodule AshPostgres.Mix.Helpers do @moduledoc false def domains!(opts, args) do apps = diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index 9ac3e9f4..182360eb 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -35,7 +35,7 @@ defmodule Mix.Tasks.AshPostgres.Create do {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) repos = - AshPostgres.MixHelpers.repos!(opts, args) + AshPostgres.Mix.Helpers.repos!(opts, args) |> Enum.filter(fn repo -> repo.create? end) repo_args = @@ -43,7 +43,7 @@ defmodule Mix.Tasks.AshPostgres.Create do ["-r", to_string(repo)] end) - rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains") + rest_opts = AshPostgres.Mix.Helpers.delete_arg(args, "--domains") Mix.Task.reenable("ecto.create") Mix.Task.run("ecto.create", repo_args ++ rest_opts) diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 16c3777c..36669f99 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -45,7 +45,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do opts = Keyword.merge(@default_opts, opts) repos = - AshPostgres.MixHelpers.repos!(opts, args) + AshPostgres.Mix.Helpers.repos!(opts, args) |> Enum.filter(fn repo -> repo.drop? end) repo_args = @@ -53,7 +53,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do ["-r", to_string(repo)] end) - rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains") + rest_opts = AshPostgres.Mix.Helpers.delete_arg(args, "--domains") Mix.Task.reenable("ecto.drop") Mix.Task.run("ecto.drop", repo_args ++ rest_opts) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 33ebb225..4fd67b61 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -100,7 +100,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do ] ) - domains = AshPostgres.MixHelpers.domains!(opts, args) + domains = AshPostgres.Mix.Helpers.domains!(opts, args) opts = opts diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index d4530254..75fa6f46 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -1,7 +1,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do use Mix.Task - import AshPostgres.MixHelpers, + import AshPostgres.Mix.Helpers, only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] @shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) domains" @@ -103,15 +103,15 @@ defmodule Mix.Tasks.AshPostgres.Migrate do def run(args) do {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) - repos = AshPostgres.MixHelpers.repos!(opts, args) + repos = AshPostgres.Mix.Helpers.repos!(opts, args) rest_opts = args - |> AshPostgres.MixHelpers.delete_arg("--domains") - |> AshPostgres.MixHelpers.delete_arg("--migrations-path") - |> AshPostgres.MixHelpers.delete_flag("--tenants") - |> AshPostgres.MixHelpers.delete_flag("--only-tenants") - |> AshPostgres.MixHelpers.delete_flag("--except-tenants") + |> AshPostgres.Mix.Helpers.delete_arg("--domains") + |> AshPostgres.Mix.Helpers.delete_arg("--migrations-path") + |> AshPostgres.Mix.Helpers.delete_flag("--tenants") + |> AshPostgres.Mix.Helpers.delete_flag("--only-tenants") + |> AshPostgres.Mix.Helpers.delete_flag("--except-tenants") Mix.Task.reenable("ecto.migrate") @@ -119,7 +119,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do for repo <- repos do Ecto.Migrator.with_repo(repo, fn repo -> for tenant <- tenants(repo, opts) do - rest_opts = AshPostgres.MixHelpers.delete_arg(rest_opts, "--prefix") + rest_opts = AshPostgres.Mix.Helpers.delete_arg(rest_opts, "--prefix") Mix.Task.run( "ecto.migrate", diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index b01d91d8..839f2b47 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -1,7 +1,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do use Mix.Task - import AshPostgres.MixHelpers, + import AshPostgres.Mix.Helpers, only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] @shortdoc "Rolls back the repository migrations for all repositories in the provided (or configured) domains" @@ -62,15 +62,15 @@ defmodule Mix.Tasks.AshPostgres.Rollback do aliases: [n: :step, v: :to] ) - repos = AshPostgres.MixHelpers.repos!(opts, args) + repos = AshPostgres.Mix.Helpers.repos!(opts, args) rest_opts = args - |> AshPostgres.MixHelpers.delete_arg("--domains") - |> AshPostgres.MixHelpers.delete_arg("--migrations-path") - |> AshPostgres.MixHelpers.delete_flag("--tenants") - |> AshPostgres.MixHelpers.delete_flag("--only-tenants") - |> AshPostgres.MixHelpers.delete_flag("--except-tenants") + |> AshPostgres.Mix.Helpers.delete_arg("--domains") + |> AshPostgres.Mix.Helpers.delete_arg("--migrations-path") + |> AshPostgres.Mix.Helpers.delete_flag("--tenants") + |> AshPostgres.Mix.Helpers.delete_flag("--only-tenants") + |> AshPostgres.Mix.Helpers.delete_flag("--except-tenants") Mix.Task.reenable("ecto.rollback") @@ -78,7 +78,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do for repo <- repos do Ecto.Migrator.with_repo(repo, fn repo -> for tenant <- tenants(repo, opts) do - rest_opts = AshPostgres.MixHelpers.delete_arg(rest_opts, "--prefix") + rest_opts = AshPostgres.Mix.Helpers.delete_arg(rest_opts, "--prefix") Mix.Task.run( "ecto.rollback", diff --git a/mix.exs b/mix.exs index 638a3b2b..485653a3 100644 --- a/mix.exs +++ b/mix.exs @@ -68,7 +68,7 @@ defmodule AshPostgres.MixProject do defp docs do [ - main: "get-started-with-postgres", + main: "home", source_ref: "v#{@version}", logo: "logos/small-logo.png", before_closing_head_tag: fn type -> @@ -87,23 +87,44 @@ defmodule AshPostgres.MixProject do end end, extras: [ + {"documentation/home.md", title: "Home"}, "documentation/tutorials/get-started-with-postgres.md", - "documentation/how_to/join-manual-relationships.md", - "documentation/how_to/test-with-postgres.md", - "documentation/how_to/using-fragments.md", - "documentation/topics/migrations_and_tasks.md", - "documentation/topics/polymorphic_resources.md", - "documentation/topics/postgres-expressions.md", - "documentation/topics/references.md", - "documentation/topics/schema-based-multitenancy.md", + "documentation/topics/resources/references.md", + "documentation/topics/resources/polymorphic-resources.md", + "documentation/topics/development/migrations-and-tasks.md", + "documentation/topics/development/testing.md", + "documentation/topics/development/upgrading-to-2.0.md", + "documentation/topics/advanced/expressions.md", + "documentation/topics/advanced/schema-based-multitenancy.md", + "documentation/topics/advanced/manual-relationships.md", "documentation/dsls/DSL:-AshPostgres.DataLayer.md", "CHANGELOG.md" ], groups_for_extras: [ - Tutorials: ~r'documentation/tutorials', - "How To": ~r'documentation/how_to', - Topics: ~r'documentation/topics', - DSLs: ~r'documentation/dsls' + Tutorials: [ + "documentation/tutorials/get-started-with-postgres.md" + ], + Resources: [ + "documentation/topics/resources/references.md", + "documentation/topics/resources/polymorphic-resources.md" + ], + Development: [ + "documentation/topics/development/migrations-and-tasks.md", + "documentation/topics/development/testing.md", + "documentation/topics/development/upgrading-to-2.0.md" + ], + Advanced: [ + "documentation/topics/advanced/expressions.md", + "documentation/topics/advanced/schema-based-multitenancy.md", + "documentation/topics/advanced/manual-relationships.md" + ], + Reference: [ + "documentation/dsls/DSL:-AshPostgres.DataLayer.md" + ] + ], + skip_undefined_reference_warnings_on: [ + "CHANGELOG.md", + "documentation/development/upgrading-to-2.0.md" ], nest_modules_by_prefix: [ AshPostgres.Functions diff --git a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs index 3222fd88..c3708b31 100644 --- a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs +++ b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs @@ -107,7 +107,7 @@ defmodule AshPostgres.TestRepo.Migrations.Install5Extensions do # Uncomment this if you actually want to uninstall the extensions # when this migration is rolled back: execute( - "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE) ash_trim_whitespace(text[])" + "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])" ) # execute("DROP EXTENSION IF EXISTS \"uuid-ossp\"") @@ -117,4 +117,4 @@ defmodule AshPostgres.TestRepo.Migrations.Install5Extensions do DROP FUNCTION IF EXISTS ash_demo_functions() """) end -end \ No newline at end of file +end From 6d4c642522342984842d77e482d0c5b8c7eeccc8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Apr 2024 20:49:39 -0400 Subject: [PATCH 0344/1215] docs: update docs for mix tasks --- .../development/migrations-and-tasks.md | 48 ++++++++----------- lib/data_layer.ex | 41 ++++++++++++---- 2 files changed, 51 insertions(+), 38 deletions(-) diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 694a2dd4..815e3351 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -6,32 +6,19 @@ ## Tasks -The available tasks are: - -- `mix ash_postgres.generate_migrations` -- `mix ash_postgres.create` -- `mix ash_postgres.drop` -- `mix ash_postgres.migrate` (use `mix ash_postgres.migrate --tenants` to run tenant migrations) - -AshPostgres is built on top of ecto, so much of its behavior is pass-through/orchestration of that tooling. +Ash comes with its own tasks, and AshPostgres exposes lower level tasks that you can use if necessary. This guide shows the process using `ash.*` tasks, and the `ash_postgres.*` tasks are illustrated at the bottom. ## Basic Workflow - Make resource changes -- Run `mix ash_postgres.generate_migrations` to generate migrations and resource snapshots -- Run `mix ash_postgres.migrate` to run those migrations -- Run `mix ash_postgres.migrate --tenants` _as well_ if you have multi-tenant resources. - -For more information on generating migrations, see the module documentation here: -`Mix.Tasks.AshPostgres.GenerateMigrations`, or run `mix help ash_postgres.generate_migrations` - -For running your migrations, there is a mix task that will find all of the repos configured in your domains and run their -migrations. It is a thin wrapper around `mix ecto.migrate`. Ours is called `mix ash_postgres.migrate` +- Run `mix ash.codegen --name add_a_combobulator` to generate migrations and resource snapshots +- Run `mix ash.migrate` to run those migrations -If you want to run or rollback individual migrations, use the corresponding +For more information on generating migrations, run `mix help ash_postgres.generate_migrations` (the underlying task that is called by `mix ash.migrate`) -For tenant migrations (see the multitenancy guides for more) generated by multitenant resources, make sure you are using -`mix ash_postgres.generate_migrations`. It is not sufficient to run `mix ash_postgres.migrate --migrations_path tenant_migrations_path`. You will also need to define a `list_tenants/0` function in your repo module. See `AshPostgres.Repo` for more. +> ### list_tenants/0 {: .info} +> +> If you have are using schema-based multitenancy, you will also need to define a `list_tenants/0` function in your repo module. See `AshPostgres.Repo` for more. ### Regenerating Migrations @@ -44,28 +31,23 @@ Often, you will run into a situation where you want to make a slight change to a N_MIGRATIONS=$(git ls-files --others priv/repo/migrations | wc -l) # Rollback untracked migrations -mix ecto.rollback -n $N_MIGRATIONS +mix ash_postgres.rollback -n $N_MIGRATIONS # Delete untracked migrations and snapshots git ls-files --others priv/repo/migrations | xargs rm git ls-files --others priv/resource_snapshots | xargs rm # Regenerate migrations -mix ash_postgres.generate_migrations +mix ash.codegen --name $1 # Run migrations if flag if echo $* | grep -e "-m" -q then - mix ecto.migrate + mix ash.migrate fi ``` -After saving this file to something like `regen.sh`, make it executable with `chmod +x regen.sh`. Now you can run it with `./regen.sh`. If you would like the migrations to automatically run after regeneration, add the `-m` flag: `./regen.sh -m`. - -## Multiple Repos - -If you are using multiple repos, you will likely need to use `mix ecto.migrate` and manage it separately for each repo, as the options would -be applied to both repo, which wouldn't make sense. +After saving this file to something like `regen.sh`, make it executable with `chmod +x regen.sh`. Now you can run it with `./regen.sh name_of_operation`. If you would like the migrations to automatically run after regeneration, add the `-m` flag: `./regen.sh name_of_operation -m`. ## Running Migrations in Production @@ -164,3 +146,11 @@ Tasks that need to be executed in the released application (because mix is not p end end ``` + +### AshPostgres-specific mix tasks + +- `mix ash_postgres.generate_migrations` +- `mix ash_postgres.create` +- `mix ash_postgres.drop` +- `mix ash_postgres.migrate` (use `mix ash_postgres.migrate --tenants` to run tenant migrations) +- `mix ash_postgres.rollback` (use `mix ash_postgres.rollback --tenants` to rollback tenant migrations) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index bf4f1f33..d4e51f00 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -420,15 +420,18 @@ defmodule AshPostgres.DataLayer do |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) n = - Mix.shell().prompt(""" - How many migrations should be rolled back#{for_repo}? (default: 0) + Mix.shell().prompt( + """ + How many migrations should be rolled back#{for_repo}? (default: 0) - Last 20 migration names, with the input you must provide to - rollback up to *and including* that migration: + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: - #{Enum.join(files, "\n")} - Rollback to: - """ |> String.trim_trailing()) + #{Enum.join(files, "\n")} + Rollback to: + """ + |> String.trim_trailing() + ) |> String.trim() |> case do "" -> @@ -484,7 +487,11 @@ defmodule AshPostgres.DataLayer do end end - Mix.Task.run("ash_postgres.rollback", args ++ ["--tenants", "-r", inspect(repo), "-n", to_string(n)]) + Mix.Task.run( + "ash_postgres.rollback", + args ++ ["--tenants", "-r", inspect(repo), "-n", to_string(n)] + ) + Mix.Task.reenable("ash_postgres.rollback") end end @@ -507,7 +514,23 @@ defmodule AshPostgres.DataLayer do # TODO: take args that we care about Mix.Task.run("ash_postgres.create", args) Mix.Task.run("ash_postgres.migrate", args) - Mix.Task.run("ash_postgres.migrate", ["--tenant" | args]) + + [] + |> AshPostgres.Mix.Helpers.repos!(args) + |> Enum.all?(fn repo -> + [] + |> AshPostgres.Mix.Helpers.tenant_migrations_path(repo) + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.empty?() + end) + |> case do + true -> + :ok + + _ -> + Mix.Task.run("ash_postgres.migrate", ["--tenant" | args]) + end end def tear_down(args) do From 14edb8ec363866262ac32eceadcb7ce37278f9a3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Apr 2024 20:51:24 -0400 Subject: [PATCH 0345/1215] chore: small formatting for task output fix --- lib/data_layer.ex | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d4e51f00..4c583dfd 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -462,17 +462,20 @@ defmodule AshPostgres.DataLayer do if !Enum.empty?(tenant_files) do n = - Mix.shell().prompt(""" + Mix.shell().prompt( + """ - How many _tenant_ migrations should be rolled back#{for_repo}? (default: 0) + How many _tenant_ migrations should be rolled back#{for_repo}? (default: 0) - Last 20 migration names, with the input you must provide to - rollback up to *and including* that migration: + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: - #{Enum.join(tenant_files, "\n")} + #{Enum.join(tenant_files, "\n")} - Rollback to: - """) + Rollback to: + """ + |> String.trim_trailing() + ) |> String.trim() |> case do "" -> From 11073e768317f1488063bedd7560de5114548a8d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Apr 2024 20:53:54 -0400 Subject: [PATCH 0346/1215] chore: remove reference to out of date primer --- documentation/topics/development/migrations-and-tasks.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 815e3351..1165569a 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -1,9 +1,5 @@ # Migrations -## Migration Generator Primer - - - ## Tasks Ash comes with its own tasks, and AshPostgres exposes lower level tasks that you can use if necessary. This guide shows the process using `ash.*` tasks, and the `ash_postgres.*` tasks are illustrated at the bottom. From bd31387150070bcbe0b1322afbe8b9093b34574d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Apr 2024 07:46:16 -0400 Subject: [PATCH 0347/1215] chore: release version v2.0.0-rc.6 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dda726d..380a618d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.5...v2.0.0-rc.6) (2024-04-10) + + + + +### Bug Fixes: + +* use proper sql implementation in `default_bindings` + +### Improvements: + +* support `mix ash.rollback` with interactive rollback + ## [v2.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.4...v2.0.0-rc.5) (2024-04-05) diff --git a/mix.exs b/mix.exs index 485653a3..427e00c0 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.5" + @version "2.0.0-rc.6" def project do [ From 410de779f0b5bc227dcb619b77f88af06606b740 Mon Sep 17 00:00:00 2001 From: James Harton Date: Thu, 11 Apr 2024 09:32:34 +1200 Subject: [PATCH 0348/1215] chore: enable dependabot for mix dependencies. --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6977f1cb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: mix + directory: "/" + schedule: + interval: "daily" From 20e32fe70a34bf6e2247776318f9d5ee30fcf0d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:34:20 -0400 Subject: [PATCH 0349/1215] chore(deps-dev): bump simple_sat from 0.1.2 to 0.1.3 (#229) Bumps [simple_sat](https://github.com/ash-project/simple_sat) from 0.1.2 to 0.1.3. - [Changelog](https://github.com/ash-project/simple_sat/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/simple_sat/compare/v0.1.2...v0.1.3) --- updated-dependencies: - dependency-name: simple_sat dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 03a12944..449e5607 100644 --- a/mix.lock +++ b/mix.lock @@ -28,7 +28,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, - "simple_sat": {:hex, :simple_sat, "0.1.2", "1e6eb889929de39bad81e72914bba0ab7a7f65dd1bed206575e34a2365f3187d", [:mix], [], "hexpm", "e476182200329d55075477375d705a81186cdad4f70590d6b94765a8c154d2c0"}, + "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, "spark": {:hex, :spark, "2.1.13", "5c002181eed1c4336942267da20f4614b35c40aa347d35183190a7496196f802", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "2d5580313bbf6717d650a27554a66c83e10d164e7087e3c4082cdb23b5dc5c64"}, From b8ddcf1fc15f51d4f3d55ac31e6c5c72fff2a145 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:34:33 -0400 Subject: [PATCH 0350/1215] chore(deps): bump ash from 3.0.0-rc.14 to 3.0.0-rc.18 (#230) Bumps [ash](https://github.com/ash-project/ash) from 3.0.0-rc.14 to 3.0.0-rc.18. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.0.0-rc.14...v3.0.0-rc.18) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 449e5607..880ff74f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.14", "c7c9441e5ff51f6c67ba4cb642689c7c7bbeedaa3d488d40cc03a1a9b23134e4", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.8", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a252d6d6518a8cc3aa893b57bb484a254ed3248c8d81a3c66e7ce0f4b68c7a1"}, + "ash": {:hex, :ash, "3.0.0-rc.18", "61e70e47194a701a2aaf92955fb466f1ce3e3f15e73626f55ebe72c2d9f09701", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "45c52d16f1d5246e65783006b43ee7ab828b1c47e954bd74569d9d5f506567e0"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.4", "74ef18e2057621e48a8930d2b765219eabf9424382e571dd2566009fe2ee76ab", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "329065f9d119f55444908933cfb9e4a47ea82bfd23ac46c040d517f669d9d385"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -31,8 +31,8 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.13", "5c002181eed1c4336942267da20f4614b35c40aa347d35183190a7496196f802", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "2d5580313bbf6717d650a27554a66c83e10d164e7087e3c4082cdb23b5dc5c64"}, - "splode": {:hex, :splode, "0.2.1", "020079ec06c9e00f8b6586852e781b5e07aee6ba588f3f45dd993831c87b0511", [:mix], [], "hexpm", "d232a933666061fe1f659d9906042fa94b9b393bb1129a4fde6fa680033b2611"}, + "spark": {:hex, :spark, "2.1.16", "3d41a184e904f29709aa759bc7f84c969b2e8513dc3510b2d6e6674bb1bfb2d9", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "32aa906ebf707f0bb2f28b93c37e05533a6f74df35cea8ae100f478caba5d0b7"}, + "splode": {:hex, :splode, "0.2.2", "cda6709f829e3fe39a9550e8c8bc11821f994ecd660b5a0d60452770f227b9ca", [:mix], [], "hexpm", "8e02f47fac4bff7cfd29a65611ee3ab728dcc9c70a5c2e438addb8f25713265a"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, From 90923d20c4c7be06924ee88d65393bbd287aaf3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:34:42 -0400 Subject: [PATCH 0351/1215] chore(deps-dev): bump excoveralls from 0.18.0 to 0.18.1 (#231) Bumps [excoveralls](https://github.com/parroty/excoveralls) from 0.18.0 to 0.18.1. - [Release notes](https://github.com/parroty/excoveralls/releases) - [Changelog](https://github.com/parroty/excoveralls/blob/master/CHANGELOG.md) - [Commits](https://github.com/parroty/excoveralls/compare/v0.18.0...v0.18.1) --- updated-dependencies: - dependency-name: excoveralls dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 880ff74f..0c0d19c5 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, - "excoveralls": {:hex, :excoveralls, "0.18.0", "b92497e69465dc51bc37a6422226ee690ab437e4c06877e836f1c18daeb35da9", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1109bb911f3cb583401760be49c02cbbd16aed66ea9509fc5479335d284da60b"}, + "excoveralls": {:hex, :excoveralls, "0.18.1", "a6f547570c6b24ec13f122a5634833a063aec49218f6fff27de9df693a15588c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d65f79db146bb20399f23046015974de0079668b9abb2f5aac074d078da60b8d"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, From e976ae5794a8fd5390c9411afd3da654f2cc2826 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 11 Apr 2024 11:39:49 -0400 Subject: [PATCH 0352/1215] fix: update ash_sql, fix credo --- lib/data_layer.ex | 6 +++--- mix.exs | 4 +--- mix.lock | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 4c583dfd..c436791e 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -442,7 +442,7 @@ defmodule AshPostgres.DataLayer do String.to_integer(n) rescue _ -> - raise "Required an integer value, got: #{n}" + reraise "Required an integer value, got: #{n}", __STACKTRACE__ end end @@ -486,7 +486,7 @@ defmodule AshPostgres.DataLayer do String.to_integer(n) rescue _ -> - raise "Required an integer value, got: #{n}" + reraise "Required an integer value, got: #{n}", __STACKTRACE__ end end @@ -1745,7 +1745,7 @@ defmodule AshPostgres.DataLayer do case bulk_create(resource, [changeset], %{ single?: true, - tenant: changeset.tenant, + tenant: Map.get(changeset, :to_tenant, changeset.tenant), return_records?: true }) do {:ok, [result]} -> diff --git a/mix.exs b/mix.exs index 427e00c0..ccca8e7d 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,6 @@ defmodule AshPostgres.MixProject do start_permanent: Mix.env() == :prod, deps: deps(), description: @description, - test_coverage: [tool: ExCoveralls], elixirc_paths: elixirc_paths(Mix.env()), preferred_cli_env: [ coveralls: :test, @@ -188,8 +187,7 @@ defmodule AshPostgres.MixProject do {:ex_check, "~> 0.14", only: [:dev, :test]}, {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false}, - {:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false}, - {:excoveralls, "~> 0.14", only: [:dev, :test]} + {:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false} ] end diff --git a/mix.lock b/mix.lock index 0c0d19c5..e57d9743 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.18", "61e70e47194a701a2aaf92955fb466f1ce3e3f15e73626f55ebe72c2d9f09701", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "45c52d16f1d5246e65783006b43ee7ab828b1c47e954bd74569d9d5f506567e0"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.4", "74ef18e2057621e48a8930d2b765219eabf9424382e571dd2566009fe2ee76ab", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "329065f9d119f55444908933cfb9e4a47ea82bfd23ac46c040d517f669d9d385"}, + "ash": {:hex, :ash, "3.0.0-rc.19", "b970b573505783bd76210f696856ec2a4a1ea4f7210bba2304696b9960911694", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cd0c28da23beba44a9fea783a92d7078f2c88d77c3a903fcc18acbe6e057f31d"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.5", "2271ab1ccf85f1710d4f4b889f8d73875059b731616e4f5cd75cddb2bea33863", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a7affd1ce0dd0840c6fd88889b2f1c553d32976e02504b9bb55619da6aec4c6b"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -16,7 +16,6 @@ "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, - "excoveralls": {:hex, :excoveralls, "0.18.1", "a6f547570c6b24ec13f122a5634833a063aec49218f6fff27de9df693a15588c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d65f79db146bb20399f23046015974de0079668b9abb2f5aac074d078da60b8d"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, From 3692dab287a53478916bd2423ae9eaa60566f202 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 11 Apr 2024 11:49:45 -0400 Subject: [PATCH 0353/1215] chore: fix test app --- mix.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/mix.exs b/mix.exs index ccca8e7d..ee8f4d98 100644 --- a/mix.exs +++ b/mix.exs @@ -44,7 +44,6 @@ defmodule AshPostgres.MixProject do if Mix.env() == :test do def application() do [ - extra_applications: [:tools, :xmerl], mod: {AshPostgres.TestApp, []} ] end From f363995f9419689c03835aed0527e68ab28af358 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Apr 2024 09:08:02 -0400 Subject: [PATCH 0354/1215] docs: update docs to point at new tasks --- documentation/topics/advanced/schema-based-multitenancy.md | 2 +- documentation/topics/resources/polymorphic-resources.md | 2 +- documentation/tutorials/get-started-with-postgres.md | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/documentation/topics/advanced/schema-based-multitenancy.md b/documentation/topics/advanced/schema-based-multitenancy.md index fae8e907..2393ebaa 100644 --- a/documentation/topics/advanced/schema-based-multitenancy.md +++ b/documentation/topics/advanced/schema-based-multitenancy.md @@ -2,7 +2,7 @@ Multitenancy in AshPostgres is implemented via postgres schemas. For more information on schemas, see postgres' [schema documentation](https://www.postgresql.org/docs/current/ddl-schemas.html) -Implementing multitenancy via schema's involves tracking "tenant migrations" separately from migrations for your public schema. You can see what this looks like by simply creating a multitenant resource, and using the migration generator `mix ash_postgres.generate_migrations --apis My.Api`. It will put schema specific migrations in `priv/repo/tenant_migrations`. When you generate migrations, you'll want to be sure to audit migrations in both directories. Additionally, when you deploy, you'll want to run your migrations, as well as running them with the migrations path `priv/repo/tenant_migrations`. +Implementing multitenancy via schema's involves tracking "tenant migrations" separately from migrations for your public schema. You can see what this looks like by simply creating a multitenant resource, and using the migration generator `mix ash.codegen`. It will put schema specific migrations in `priv/repo/tenant_migrations`. When you generate migrations, you'll want to be sure to audit migrations in both directories. Additionally, when you deploy, you'll want to run your migrations, as well as running them with the migrations path `priv/repo/tenant_migrations`. ## Generated migrations diff --git a/documentation/topics/resources/polymorphic-resources.md b/documentation/topics/resources/polymorphic-resources.md index 038f1cd8..e7852795 100644 --- a/documentation/topics/resources/polymorphic-resources.md +++ b/documentation/topics/resources/polymorphic-resources.md @@ -83,4 +83,4 @@ When a migration is marked as `polymorphic? true`, the migration generator will all resources that are related to it, that set the `%{data_layer: %{table: "table"}}` context. For each of those, a migration is generated/managed automatically. This means that adding reactions to a new resource is as easy as adding the relationship and table context, and then running -`mix ash_postgres.generate_migrations`. +`mix ash.codegen`. diff --git a/documentation/tutorials/get-started-with-postgres.md b/documentation/tutorials/get-started-with-postgres.md index 13e2fccd..8fdd1124 100644 --- a/documentation/tutorials/get-started-with-postgres.md +++ b/documentation/tutorials/get-started-with-postgres.md @@ -179,12 +179,12 @@ Now we can add the data layer to our resources. The basic configuration for a re ### Create the database and tables -First, we'll create the database with `mix ash_postgres.create`. +First, we'll create the database with `mix ash.setup`. Then we will generate database migrations. This is one of the many ways that AshPostgres can save time and reduce complexity. ```bash -mix ash_postgres.generate_migrations --name add_tickets_and_representatives +mix ash.codegen --name add_tickets_and_representatives ``` If you are unfamiliar with database migrations, it is a good idea to get a rough idea of what they are and how they work. See the links at the bottom of this guide for more. A rough overview of how migrations work is that each time you need to make changes to your database, they are saved as small, reproducible scripts that can be applied in order. This is necessary both for clean deploys as well as working with multiple developers making changes to the structure of a single database. @@ -196,8 +196,7 @@ You should always look at the generated migrations to ensure that they look corr Finally, we will create the local database and apply the generated migrations: ```bash -mix ash_postgres.create -mix ash_postgres.migrate +mix ash.setup ``` ### Try it out From 8b6737c42d206b6a8bc5839a79c836e9baa8b159 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Apr 2024 09:08:49 -0400 Subject: [PATCH 0355/1215] chore: release version v2.0.0-rc.7 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 380a618d..81435e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2024-04-12) + + + + +### Bug Fixes: + +* update ash_sql, fix credo + ## [v2.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.5...v2.0.0-rc.6) (2024-04-10) diff --git a/mix.exs b/mix.exs index ee8f4d98..dcf0ff0e 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.6" + @version "2.0.0-rc.7" def project do [ From e840258a9b31053aa3fef2d13ddbcd310ce5096e Mon Sep 17 00:00:00 2001 From: Linus Date: Fri, 12 Apr 2024 15:14:41 +0200 Subject: [PATCH 0356/1215] docs: Improve get started tutorial (#232) - Remove repeated block of code in calculations section - Calculations can refer _aggregates_. --- documentation/tutorials/get-started-with-postgres.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/documentation/tutorials/get-started-with-postgres.md b/documentation/tutorials/get-started-with-postgres.md index 8fdd1124..05592ebd 100644 --- a/documentation/tutorials/get-started-with-postgres.md +++ b/documentation/tutorials/get-started-with-postgres.md @@ -297,11 +297,6 @@ You can also load individual aggregates on demand after queries have already bee require Ash.Query -Helpdesk.Support.Representative -|> Ash.Query.filter(closed_tickets < 4) -|> Ash.Query.sort(closed_tickets: :desc) -|> Helpdesk.Support.read!() - representatives = Helpdesk.Support.read!(Helpdesk.Support.Representative) Helpdesk.Support.load!(representatives, :open_tickets) @@ -309,7 +304,7 @@ Helpdesk.Support.load!(representatives, :open_tickets) ### Calculations -Calculations can be pushed down into SQL in the same way. Calculations are similar to aggregates, except they work on individual records. They can, however, refer to calculations on the resource, which opens up powerful possibilities with very simple code. +Calculations can be pushed down into SQL in the same way. Calculations are similar to aggregates, except they work on individual records. They can, however, refer to aggregates on the resource, which opens up powerful possibilities with very simple code. For example, we can determine the percentage of tickets that are open: From e86f250ae56dd4a22bda955d826d66a3083039ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:49:17 +0100 Subject: [PATCH 0357/1215] chore(deps): bump ash_sql from 0.1.1-rc.5 to 0.1.1-rc.6 (#234) Bumps [ash_sql](https://github.com/ash-project/ash_sql) from 0.1.1-rc.5 to 0.1.1-rc.6. - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.1.1-rc.5...v0.1.1-rc.6) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index e57d9743..640addca 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.19", "b970b573505783bd76210f696856ec2a4a1ea4f7210bba2304696b9960911694", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cd0c28da23beba44a9fea783a92d7078f2c88d77c3a903fcc18acbe6e057f31d"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.5", "2271ab1ccf85f1710d4f4b889f8d73875059b731616e4f5cd75cddb2bea33863", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a7affd1ce0dd0840c6fd88889b2f1c553d32976e02504b9bb55619da6aec4c6b"}, + "ash": {:hex, :ash, "3.0.0-rc.21", "f108d0844f137985dac913b74ea81a15c1cb372924b285c031293829e8f27bbe", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "93109001c20eff532a8368c646a7e5cc9d598e2f508451de00ca056fad19c152"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.6", "350aa338bc93ccdcd41a5a7d4aef65d54ef37881464fc9ad8168cdd92744f846", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "209ae466b744c81036d83b436065c86a2fff7453b7cfbe3b7d020b20c2443ec7"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -29,8 +29,8 @@ "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, - "spark": {:hex, :spark, "2.1.16", "3d41a184e904f29709aa759bc7f84c969b2e8513dc3510b2d6e6674bb1bfb2d9", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "32aa906ebf707f0bb2f28b93c37e05533a6f74df35cea8ae100f478caba5d0b7"}, + "sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"}, + "spark": {:hex, :spark, "2.1.18", "2836a5a4b2874e5493a80fdcb84cfe9f1b89a45382e042e40d117d10000bb83e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f0060725e20b8d330734ac16da048022e2f37df03b74334725c40cab43c3fa6d"}, "splode": {:hex, :splode, "0.2.2", "cda6709f829e3fe39a9550e8c8bc11821f994ecd660b5a0d60452770f227b9ca", [:mix], [], "hexpm", "8e02f47fac4bff7cfd29a65611ee3ab728dcc9c70a5c2e438addb8f25713265a"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, From d36c2d1538784550a35e4ff755bc9a43175f5662 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 17 Apr 2024 02:00:12 +0100 Subject: [PATCH 0358/1215] docs: add `ash-functions` to the extensions list in getting started --- documentation/tutorials/get-started-with-postgres.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/documentation/tutorials/get-started-with-postgres.md b/documentation/tutorials/get-started-with-postgres.md index 05592ebd..58f00b4c 100644 --- a/documentation/tutorials/get-started-with-postgres.md +++ b/documentation/tutorials/get-started-with-postgres.md @@ -46,6 +46,12 @@ Create `lib/helpdesk/repo.ex` with the following contents. `AshPostgres.Repo` is defmodule Helpdesk.Repo do use AshPostgres.Repo, otp_app: :helpdesk + + def installed_extensions do + # Ash installs some functions that it needs to run the + # first time you generate migrations. + ["ash-functions"] + end end ``` From 4d18f56baf5088ff8f4e14e1722501e5d98bb50e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:31:53 +0100 Subject: [PATCH 0359/1215] chore(deps): bump ash from 3.0.0-rc.21 to 3.0.0-rc.25 (#241) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 640addca..8fc75158 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.21", "f108d0844f137985dac913b74ea81a15c1cb372924b285c031293829e8f27bbe", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "93109001c20eff532a8368c646a7e5cc9d598e2f508451de00ca056fad19c152"}, + "ash": {:hex, :ash, "3.0.0-rc.25", "eb038903b56d05c619510d55db6ace5b5c563f9bd04f9f160859128e67ce34d0", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "59c7f617cffa826d81713d52bbfb0686d943faf108d90499a7272b5a44b77c79"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.6", "350aa338bc93ccdcd41a5a7d4aef65d54ef37881464fc9ad8168cdd92744f846", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "209ae466b744c81036d83b436065c86a2fff7453b7cfbe3b7d020b20c2443ec7"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 4957ed28f3ad05ba0e83c96e4d30e3f5efc31dc9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 20 Apr 2024 14:37:46 +0100 Subject: [PATCH 0360/1215] fix: ensure that `exists` with a filter paired with `from_many?` functions properly --- mix.exs | 4 ++-- mix.lock | 2 +- test/filter_test.exs | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index dcf0ff0e..c717a71f 100644 --- a/mix.exs +++ b/mix.exs @@ -172,8 +172,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0.0-rc")}, - {:ash_sql, "~> 0.1.1-rc"}, + {:ash, ash_version("~> 3.0.0-rc.27")}, + {:ash_sql, path: "../ash_sql"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 8fc75158..9b3579d7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.25", "eb038903b56d05c619510d55db6ace5b5c563f9bd04f9f160859128e67ce34d0", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "59c7f617cffa826d81713d52bbfb0686d943faf108d90499a7272b5a44b77c79"}, + "ash": {:hex, :ash, "3.0.0-rc.27", "b6ff59bd6a78e566536587bb11986107393b42001972dd22fe6e7f2402061ee7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9c5eea5756947aae8ee0131e13fcdb7c9af3ab563b5eb3a04dec924dd195a57"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.6", "350aa338bc93ccdcd41a5a7d4aef65d54ef37881464fc9ad8168cdd92744f846", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "209ae466b744c81036d83b436065c86a2fff7453b7cfbe3b7d020b20c2443ec7"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/filter_test.exs b/test/filter_test.exs index 199c35ae..a7211bbc 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -989,5 +989,32 @@ defmodule AshPostgres.FilterTest do |> Ash.Query.filter(first_member.id != ^cm2.id) |> Ash.read!() |> length == 4 + + assert Channel + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(first_member.id == ^cm2.id) + |> Ash.read!() + |> length == 1 + end + + test "using exists with from_many?" do + c = Ash.Changeset.for_create(Channel, :create, %{}) |> Ash.create!() + + [cm1, cm2 | _] = + for _ <- 1..5 do + Ash.Changeset.for_create(ChannelMember, :create, %{channel_id: c.id}) |> Ash.create!() + end + + assert Channel + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(exists(first_member, id == ^cm2.id)) + |> Ash.read!() + |> length == 0 + + assert Channel + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(exists(first_member, id == ^cm1.id)) + |> Ash.read!() + |> length == 1 end end From 48a31b17da7480275a7a7a5aa80918f6e4fcf134 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 20 Apr 2024 14:44:19 +0100 Subject: [PATCH 0361/1215] chore: fix dep --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index c717a71f..d55485c3 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc.27")}, - {:ash_sql, path: "../ash_sql"}, + {:ash_sql, "~> 0.1.1-rc.7"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, From a66a0f694c684d87a5a0e79e45b8e4971d0a6b54 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 20 Apr 2024 15:20:59 +0100 Subject: [PATCH 0362/1215] chore: update deps --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 9b3579d7..dd003142 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.27", "b6ff59bd6a78e566536587bb11986107393b42001972dd22fe6e7f2402061ee7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9c5eea5756947aae8ee0131e13fcdb7c9af3ab563b5eb3a04dec924dd195a57"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.6", "350aa338bc93ccdcd41a5a7d4aef65d54ef37881464fc9ad8168cdd92744f846", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "209ae466b744c81036d83b436065c86a2fff7453b7cfbe3b7d020b20c2443ec7"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.7", "a8c6d570ea36d1f2921c130888a3d3973268e900ca02c2da495075af9dc30cd7", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8135fe4f3ea8948f4d1bbcaf61d5ebcd350957f4873cb5d2d5f8aeb213929d2b"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 8f1ae86db9a24afb7f8ff5566c30a2551f49acac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 21 Apr 2024 09:35:47 -0400 Subject: [PATCH 0363/1215] test: add some explicit destroy/soft destroy tests --- test/destroy_test.exs | 43 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 28 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 test/destroy_test.exs diff --git a/test/destroy_test.exs b/test/destroy_test.exs new file mode 100644 index 00000000..87ed72d0 --- /dev/null +++ b/test/destroy_test.exs @@ -0,0 +1,43 @@ +defmodule AshPostgres.DestroyTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Post + + test "destroy action destroys the record" do + post = + Post + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + post + |> Ash.Changeset.for_destroy(:destroy, %{}) + |> Ash.destroy!() + + assert [] = Ash.read!(Post) + end + + test "before action hooks are honored" do + post = + Post + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + assert_raise Ash.Error.Invalid, ~r/must type CONFIRM/, fn -> + post + |> Ash.Changeset.for_destroy(:destroy_with_confirm, %{confirm: "NOT CONFIRM"}) + |> Ash.destroy!() + end + end + + test "before action hooks are honored, for soft destroys as well" do + post = + Post + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + assert_raise Ash.Error.Invalid, ~r/must type CONFIRM/, fn -> + post + |> Ash.Changeset.for_destroy(:soft_destroy_with_confirm, %{confirm: "NOT CONFIRM"}) + |> Ash.destroy!() + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 371f8497..3032ae7d 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -78,6 +78,34 @@ defmodule AshPostgres.Test.Post do defaults([:destroy]) + destroy :destroy_with_confirm do + argument(:confirm, :string, allow_nil?: false) + + change(fn changeset, _ -> + Ash.Changeset.before_action(changeset, fn changeset -> + if changeset.arguments.confirm == "CONFIRM" do + changeset + else + Ash.Changeset.add_error(changeset, field: :confirm, message: "must type CONFIRM") + end + end) + end) + end + + destroy :soft_destroy_with_confirm do + argument(:confirm, :string, allow_nil?: false) + + change(fn changeset, _ -> + Ash.Changeset.before_action(changeset, fn changeset -> + if changeset.arguments.confirm == "CONFIRM" do + changeset + else + Ash.Changeset.add_error(changeset, field: :confirm, message: "must type CONFIRM") + end + end) + end) + end + update :update do primary?(true) require_atomic?(false) From 90ae9aeef9a9230a5d1c5664c791896a02cd8a7a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 21 Apr 2024 09:56:19 -0400 Subject: [PATCH 0364/1215] chore: add mix_audit --- mix.exs | 1 + mix.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/mix.exs b/mix.exs index d55485c3..3e01aec8 100644 --- a/mix.exs +++ b/mix.exs @@ -185,6 +185,7 @@ defmodule AshPostgres.MixProject do {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, + {:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index dd003142..e5255feb 100644 --- a/mix.lock +++ b/mix.lock @@ -24,6 +24,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, @@ -36,4 +37,6 @@ "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, + "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, } From 7f3661ce42ac656cb38d098170ccdd27e4bfb848 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 21 Apr 2024 11:11:00 -0400 Subject: [PATCH 0365/1215] chore: support `adapter` option to `use AshPostgres.Repo` --- lib/repo.ex | 2 +- test/support/resources/post.ex | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/repo.ex b/lib/repo.ex index 408ab54b..c2bd1831 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -82,7 +82,7 @@ defmodule AshPostgres.Repo do otp_app = opts[:otp_app] || raise("Must configure OTP app") use Ecto.Repo, - adapter: Ecto.Adapters.Postgres, + adapter: opts[:adapter] || Ecto.Adapters.Postgres, otp_app: otp_app end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 3032ae7d..d1b56931 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -79,6 +79,7 @@ defmodule AshPostgres.Test.Post do defaults([:destroy]) destroy :destroy_with_confirm do + require_atomic?(false) argument(:confirm, :string, allow_nil?: false) change(fn changeset, _ -> @@ -93,6 +94,7 @@ defmodule AshPostgres.Test.Post do end destroy :soft_destroy_with_confirm do + require_atomic?(false) argument(:confirm, :string, allow_nil?: false) change(fn changeset, _ -> From 3007ebf1bf7a4eb7e5d265074916de5d1b97608b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 11:31:13 -0400 Subject: [PATCH 0366/1215] chore: update ash_sql dependency --- mix.exs | 21 ++++++++++++++++++++- mix.lock | 2 +- test/calculation_test.exs | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 3e01aec8..7c0d3a0c 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc.27")}, - {:ash_sql, "~> 0.1.1-rc.7"}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc.8 and >= 0.1.1-rc.8")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, @@ -210,6 +210,25 @@ defmodule AshPostgres.MixProject do end end + defp ash_sql_version(default_version) do + case System.get_env("ASH_SQL_VERSION") do + nil -> + default_version + + "local" -> + [path: "../ash_sql", override: true] + + "main" -> + [git: "https://github.com/ash-project/ash_sql.git"] + + version when is_binary(version) -> + "~> #{version}" + + version -> + version + end + end + defp aliases do [ sobelow: diff --git a/mix.lock b/mix.lock index e5255feb..c63676ee 100644 --- a/mix.lock +++ b/mix.lock @@ -31,7 +31,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"}, - "spark": {:hex, :spark, "2.1.18", "2836a5a4b2874e5493a80fdcb84cfe9f1b89a45382e042e40d117d10000bb83e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f0060725e20b8d330734ac16da048022e2f37df03b74334725c40cab43c3fa6d"}, + "spark": {:hex, :spark, "2.1.20", "204db8fd28378783c28a9dcb0bebdaf1d51b14a9ea106e1080457d29510a66ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e7a4f8f8ca7a477918af1eb65e20f2015f783a9a23e5f73d1020edf5b2ef69be"}, "splode": {:hex, :splode, "0.2.2", "cda6709f829e3fe39a9550e8c8bc11821f994ecd660b5a0d60452770f227b9ca", [:mix], [], "hexpm", "8e02f47fac4bff7cfd29a65611ee3ab728dcc9c70a5c2e438addb8f25713265a"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index ff46dc52..bee98335 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -238,6 +238,27 @@ defmodule AshPostgres.CalculationTest do |> Ash.read_one!() end + test "concat can be used with a reference" do + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "is", + last_name: "match", + badges: [:foo, :bar] + }) + |> Ash.create!() + + badges_string = + Author + |> Ash.Query.filter(id == ^author.id) + |> Ash.Query.calculate(:badges_string, :string, expr(string_join(badges))) + |> Ash.read_one!() + |> Map.get(:calculations) + |> Map.get(:badges_string) + + assert badges_string == "foobar" + end + test "calculations that refer to aggregates in comparison expressions can be filtered on" do Post |> Ash.Query.load(:has_future_comment) From 1b44e3199a37bb6d40470909c75cbe7b59e44b22 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 11:34:04 -0400 Subject: [PATCH 0367/1215] chore: release version v2.0.0-rc.8 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81435e8c..09a781d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2024-04-22) + + + + +### Bug Fixes: + +* ensure that `exists` with a filter paired with `from_many?` functions properly + ## [v2.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2024-04-12) diff --git a/mix.exs b/mix.exs index 7c0d3a0c..81200dd1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.7" + @version "2.0.0-rc.8" def project do [ From d26a1899467b99fc678072c10221b987e85802ea Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 12:04:16 -0400 Subject: [PATCH 0368/1215] improvement: warn on missing ash-functions at compile time closes #242 --- lib/data_layer.ex | 11 +++++++++-- lib/repo.ex | 42 ++++++++++++++++++++++++++++++++++++++++++ mix.lock | 2 +- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c436791e..2a886ec6 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -571,8 +571,15 @@ defmodule AshPostgres.DataLayer do def can?(_, :transact), do: true def can?(_, :composite_primary_key), do: true - def can?(_, {:atomic, :update}), do: true - def can?(_, {:atomic, :upsert}), do: true + + def can?(resource, {:atomic, :update}) do + "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :mutate).installed_extensions() + end + + def can?(resource, {:atomic, :upsert}) do + "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :mutate).installed_extensions() + end + def can?(_, :upsert), do: true def can?(_, :changeset_filter), do: true diff --git a/lib/repo.ex b/lib/repo.ex index c2bd1831..30bfbb73 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -88,6 +88,9 @@ defmodule AshPostgres.Repo do @agent __MODULE__.AshPgVersion @behaviour AshPostgres.Repo + @warn_on_missing_ash_functions Keyword.get(opts, :warn_on_missing_ash_functions?, true) + @after_compile __MODULE__ + require Logger defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1 @@ -340,6 +343,45 @@ defmodule AshPostgres.Repo do override_migration_type: 1, create?: 0, drop?: 0 + + def __after_compile__(_, _) do + if "ash-functions" in installed_extensions() || !@warn_on_missing_ash_functions do + :ok + else + IO.warn(""" + AshPostgres: You have not installed the `ash-functions` extension. + + The following features will not be available: + + - atomics (using the `raise_ash_error` function) + - `string_trim` (using the `ash_trim_whitespace` function) + - the `||` and `&&` operators (using the `ash_elixir_and` and `ash_elixir_or` functions) + + To address this warning, do one of two things: + + 1. add the `"ash-functions"` extension to your `installed_extensions/0` function, and then generate migrations. + + def installed_extensions do + ["ash-functions"] + end + + If you are *not* using the migration generator, but would like to leverage these features, follow the above instructions, + and then visit the source for `ash_postgres` and copy the latest version of those functions into your own migrations: + + 2. disable this warning, by adding the following to your `use` statement: + + use AshPostgres.Repo, + .. + warn_on_missing_ash_functions?: false + + Keep in mind that if you disable this warning, you will not be able to use the features mentioned above. + If you are in an environment where you cannot define functions, you will have to use the second option. + + + https://github.com/ash-project/ash_postgres/blob/main/lib/migration_generator/ash_functions.ex + """) + end + end end end end diff --git a/mix.lock b/mix.lock index c63676ee..c05e02fc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.27", "b6ff59bd6a78e566536587bb11986107393b42001972dd22fe6e7f2402061ee7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9c5eea5756947aae8ee0131e13fcdb7c9af3ab563b5eb3a04dec924dd195a57"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.7", "a8c6d570ea36d1f2921c130888a3d3973268e900ca02c2da495075af9dc30cd7", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8135fe4f3ea8948f4d1bbcaf61d5ebcd350957f4873cb5d2d5f8aeb213929d2b"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.8", "0e2aae33919b0e3afdfa81d0830328582b1c3766b30951f2b78a5ca04fc541da", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e000e0019a6e765c242ee35280fc155d84f740059059a5a6f8aeb8909eedb85d"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From ec91e2ee9b6340c35c76f8ab6d99a2246703a0a3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 18:58:57 -0400 Subject: [PATCH 0369/1215] fix: reproduce issue around atomic updates & validations --- test/atomics_test.exs | 12 ++++++++++++ test/support/resources/post.ex | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 7f03507f..62828fa0 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -33,6 +33,18 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "an atomic validation is based on where it appears in the action" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "bar"}) + |> Ash.create!() + + # just asserting that there is no exception here + post + |> Ash.Changeset.for_update(:change_title_to_foo_unless_its_already_foo) + |> Ash.update!() + end + test "an atomic works with a datetime" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d1b56931..5184edf6 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -113,6 +113,11 @@ defmodule AshPostgres.Test.Post do require_atomic?(false) end + update :change_title_to_foo_unless_its_already_foo do + validate attribute_does_not_equal(:title, "foo") + change set_attribute(:title, "foo") + end + read :title_is_foo do filter(expr(title == "foo")) end From 32448ea5dee44d522d37668ae2372d2c38b6acad Mon Sep 17 00:00:00 2001 From: James Harton Date: Tue, 23 Apr 2024 11:06:06 +1200 Subject: [PATCH 0370/1215] chore: Add failing test for tenanted aggregate bug. (#244) --- test/aggregate_test.exs | 26 +++++++++++++++++++++ test/support/multitenancy/resources/post.ex | 4 ++++ test/support/multitenancy/resources/user.ex | 4 ++++ 3 files changed, 34 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index ce6df24b..1dadc922 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -444,6 +444,32 @@ defmodule AshSql.AggregateTest do |> Ash.Query.load([:count_comment_titles, :count_uniq_comment_titles]) |> Ash.read_one!() end + + test "when related data that uses schema-based multitenancy, it returns the uniq" do + alias AshPostgres.MultitenancyTest.{Org, Post, User} + + org = + Org + |> Ash.Changeset.for_create(:create, %{name: "BTTF"}) + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{name: "Marty", org_id: org.id}) + |> Ash.create!() + + posts = + ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] + |> Enum.map( + &(Post + |> Ash.Changeset.for_create(:create, %{name: &1}) + |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) + ) + + user = Ash.load!(user, :years_visited, tenant: "org_#{org.id}") + + assert Enum.sort(user.years_visited) == ["1955", "1985", "2015"] + end end describe "first" do diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index 84452d8c..35cc9adf 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -51,4 +51,8 @@ defmodule AshPostgres.MultitenancyTest.Post do has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id, public?: true) end + + calculations do + calculate(:last_word, :string, expr(fragment("split_part(?, ' ', -1)", name))) + end end diff --git a/test/support/multitenancy/resources/user.ex b/test/support/multitenancy/resources/user.ex index 05ab9670..1f872ff8 100644 --- a/test/support/multitenancy/resources/user.ex +++ b/test/support/multitenancy/resources/user.ex @@ -36,5 +36,9 @@ defmodule AshPostgres.MultitenancyTest.User do end end + aggregates do + list(:years_visited, :posts, :last_word) + end + def parse_tenant("org_" <> id), do: id end From 3c4a3d30a53c4a7e0bbf31d8e337b2426302ea1c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 20:42:11 -0400 Subject: [PATCH 0371/1215] fix: handle missing aggregate relationships and fields better in transformers fix: update ash_sql for bug fixes --- .../prevent_multidimensional_array_aggregates.ex | 11 ++++++++++- mix.exs | 2 +- test/aggregate_test.exs | 15 +++++++-------- test/support/multitenancy/resources/user.ex | 4 ++++ test/support/resources/post.ex | 4 ++-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/verifiers/prevent_multidimensional_array_aggregates.ex b/lib/verifiers/prevent_multidimensional_array_aggregates.ex index 6b267ff4..cdb688b4 100644 --- a/lib/verifiers/prevent_multidimensional_array_aggregates.ex +++ b/lib/verifiers/prevent_multidimensional_array_aggregates.ex @@ -12,7 +12,16 @@ defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do |> Stream.filter(& &1.field) |> Enum.each(fn aggregate -> related = Ash.Resource.Info.related(resource, aggregate.relationship_path) - type = Ash.Resource.Info.field(related, aggregate.field).type + + related_field = + if related do + Ash.Resource.Info.field(related, aggregate.field) + end + + type = + if related_field do + related_field.type + end case type do {:array, _} -> diff --git a/mix.exs b/mix.exs index 81200dd1..2ea8534a 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc.27")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc.8 and >= 0.1.1-rc.8")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc.8 and >= 0.1.1-rc.11")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 1dadc922..588f6e41 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -458,17 +458,16 @@ defmodule AshSql.AggregateTest do |> Ash.Changeset.for_create(:create, %{name: "Marty", org_id: org.id}) |> Ash.create!() - posts = - ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] - |> Enum.map( - &(Post - |> Ash.Changeset.for_create(:create, %{name: &1}) - |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) - ) + ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] + |> Enum.map( + &(Post + |> Ash.Changeset.for_create(:create, %{name: &1, user_id: user.id}) + |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) + ) user = Ash.load!(user, :years_visited, tenant: "org_#{org.id}") - assert Enum.sort(user.years_visited) == ["1955", "1985", "2015"] + assert Enum.sort(user.years_visited) == ["1955", "1985", "1985", "2015"] end end diff --git a/test/support/multitenancy/resources/user.ex b/test/support/multitenancy/resources/user.ex index 1f872ff8..d164c28b 100644 --- a/test/support/multitenancy/resources/user.ex +++ b/test/support/multitenancy/resources/user.ex @@ -34,6 +34,10 @@ defmodule AshPostgres.MultitenancyTest.User do belongs_to(:org, AshPostgres.MultitenancyTest.Org) do public?(true) end + + has_many :posts, AshPostgres.MultitenancyTest.Post do + public?(true) + end end aggregates do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 5184edf6..c2276dc2 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -114,8 +114,8 @@ defmodule AshPostgres.Test.Post do end update :change_title_to_foo_unless_its_already_foo do - validate attribute_does_not_equal(:title, "foo") - change set_attribute(:title, "foo") + validate(attribute_does_not_equal(:title, "foo")) + change(set_attribute(:title, "foo")) end read :title_is_foo do From 5875b17407523202e0986be7485e583b0794212c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 20:42:44 -0400 Subject: [PATCH 0372/1215] chore: release version v2.0.0-rc.9 --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a781d9..bd49e344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,63 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.8...v2.0.0-rc.9) (2024-04-23) +### Breaking Changes: + +* change defaults for uuids to `gen_random_uuid()` + +* Use UTC for default generated timestamps (#131) + +* 3.0 (#227) + + + +### Features: + +* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Bug Fixes: + +* handle missing aggregate relationships and fields better in transformers + +* update ash_sql for bug fixes + +* reproduce issue around atomic updates & validations + +* ensure that `exists` with a filter paired with `from_many?` functions properly + +* update ash_sql, fix credo + +* use proper sql implementation in `default_bindings` + +* don't wait for shell input when checking migrations + +* properly handle non-filter aggregate filters + +* ensure timestamps are present in extension migrations + +* handle fully fleshed out aggregate fields + +### Improvements: + +* warn on missing ash-functions at compile time + +* support `mix ash.rollback` with interactive rollback + +* don't fetch version in agent when using sandbox + +* loosen 3.0 release candidate requirement + +* fixes for 3.0 changes and AshSql changes + +* move many internals out to `AshSql` package + +* add default implementation for pg_version, and rename to `min_pg_version` + +* upgrade to 3.0 + +* properly show unsupported error expression + ## [v2.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2024-04-22) diff --git a/mix.exs b/mix.exs index 2ea8534a..6181499b 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.8" + @version "2.0.0-rc.9" def project do [ From 1b844d741279d33751b467d6a40875056942cc70 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Apr 2024 20:43:06 -0400 Subject: [PATCH 0373/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c05e02fc..86d634e4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.27", "b6ff59bd6a78e566536587bb11986107393b42001972dd22fe6e7f2402061ee7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9c5eea5756947aae8ee0131e13fcdb7c9af3ab563b5eb3a04dec924dd195a57"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.8", "0e2aae33919b0e3afdfa81d0830328582b1c3766b30951f2b78a5ca04fc541da", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e000e0019a6e765c242ee35280fc155d84f740059059a5a6f8aeb8909eedb85d"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.11", "d2e8ef8bd159a61226ae91af029d6abe126bd5f4abca353d92a6320e90d797f9", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ad8f70886668b1b239b082e2efeae93a5cb3d98677297accdf7d408d4c101f4"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 1ad3cd4ba020839f23a181c902afc32ba53fba8d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 23 Apr 2024 10:30:13 -0400 Subject: [PATCH 0374/1215] fix: undo change that expresses that atomics cant be done without `ash-functions` --- lib/data_layer.ex | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2a886ec6..03372acd 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -572,13 +572,8 @@ defmodule AshPostgres.DataLayer do def can?(_, :transact), do: true def can?(_, :composite_primary_key), do: true - def can?(resource, {:atomic, :update}) do - "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :mutate).installed_extensions() - end - - def can?(resource, {:atomic, :upsert}) do - "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :mutate).installed_extensions() - end + def can?(_resource, {:atomic, :update}), do: true + def can?(_resource, {:atomic, :upsert}), do: true def can?(_, :upsert), do: true def can?(_, :changeset_filter), do: true From fba7a9e4f8894493ad16ddb6397fe6ad32275cc4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 23 Apr 2024 10:38:47 -0400 Subject: [PATCH 0375/1215] chore: release version v2.0.0-rc.10 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd49e344..2a53f477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.10](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2024-04-23) + + + + +### Bug Fixes: + +* undo change that expresses that atomics cant be done without `ash-functions` + ## [v2.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.8...v2.0.0-rc.9) (2024-04-23) ### Breaking Changes: diff --git a/mix.exs b/mix.exs index 6181499b..b0376894 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.9" + @version "2.0.0-rc.10" def project do [ From 090193f4ca76ac2586b470dc6edb18b9588f1da3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 23 Apr 2024 10:57:45 -0400 Subject: [PATCH 0376/1215] chore: loosen dependency requirements --- mix.exs | 4 ++-- mix.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index b0376894..3ce7a64a 100644 --- a/mix.exs +++ b/mix.exs @@ -172,8 +172,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0.0-rc.27")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc.8 and >= 0.1.1-rc.11")}, + {:ash, ash_version("~> 3.0.0-rc")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.11")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 86d634e4..5ffb4687 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.27", "b6ff59bd6a78e566536587bb11986107393b42001972dd22fe6e7f2402061ee7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9c5eea5756947aae8ee0131e13fcdb7c9af3ab563b5eb3a04dec924dd195a57"}, + "ash": {:hex, :ash, "3.0.0-rc.28", "f42c8ef8d8485fb55781e47e7b00fcd3206e40b87f3656fdd87ebbba77b4ce62", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3f3b5407970167306fa97bf86c020722f54b28c0b01117c748808f47747723ae"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.11", "d2e8ef8bd159a61226ae91af029d6abe126bd5f4abca353d92a6320e90d797f9", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ad8f70886668b1b239b082e2efeae93a5cb3d98677297accdf7d408d4c101f4"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 673cd0312fb17cc28a4ae7139ed19dadc5f74500 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 23 Apr 2024 14:15:25 -0400 Subject: [PATCH 0377/1215] fixs: fix dialyzer warning --- lib/repo.ex | 58 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/repo.ex b/lib/repo.ex index 30bfbb73..b6a49815 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -344,42 +344,50 @@ defmodule AshPostgres.Repo do create?: 0, drop?: 0 - def __after_compile__(_, _) do - if "ash-functions" in installed_extensions() || !@warn_on_missing_ash_functions do - :ok - else - IO.warn(""" - AshPostgres: You have not installed the `ash-functions` extension. + # We do this switch because `!@warn_on_missing_ash_functions` in the function body triggers + # a dialyzer error + if @warn_on_missing_ash_functions do + def __after_compile__(_, _) do + if "ash-functions" in installed_extensions() do + :ok + else + IO.warn(""" + AshPostgres: You have not installed the `ash-functions` extension. - The following features will not be available: + The following features will not be available: - - atomics (using the `raise_ash_error` function) - - `string_trim` (using the `ash_trim_whitespace` function) - - the `||` and `&&` operators (using the `ash_elixir_and` and `ash_elixir_or` functions) + - atomics (using the `raise_ash_error` function) + - `string_trim` (using the `ash_trim_whitespace` function) + - the `||` and `&&` operators (using the `ash_elixir_and` and `ash_elixir_or` functions) - To address this warning, do one of two things: + To address this warning, do one of two things: - 1. add the `"ash-functions"` extension to your `installed_extensions/0` function, and then generate migrations. + 1. add the `"ash-functions"` extension to your `installed_extensions/0` function, and then generate migrations. - def installed_extensions do - ["ash-functions"] - end + def installed_extensions do + ["ash-functions"] + end - If you are *not* using the migration generator, but would like to leverage these features, follow the above instructions, - and then visit the source for `ash_postgres` and copy the latest version of those functions into your own migrations: + If you are *not* using the migration generator, but would like to leverage these features, follow the above instructions, + and then visit the source for `ash_postgres` and copy the latest version of those functions into your own migrations: - 2. disable this warning, by adding the following to your `use` statement: + 2. disable this warning, by adding the following to your `use` statement: - use AshPostgres.Repo, - .. - warn_on_missing_ash_functions?: false + use AshPostgres.Repo, + .. + warn_on_missing_ash_functions?: false - Keep in mind that if you disable this warning, you will not be able to use the features mentioned above. - If you are in an environment where you cannot define functions, you will have to use the second option. + Keep in mind that if you disable this warning, you will not be able to use the features mentioned above. + If you are in an environment where you cannot define functions, you will have to use the second option. - https://github.com/ash-project/ash_postgres/blob/main/lib/migration_generator/ash_functions.ex - """) + https://github.com/ash-project/ash_postgres/blob/main/lib/migration_generator/ash_functions.ex + """) + end + end + else + def __after_compile__(_, _) do + :ok end end end From d44c0573459e9effa0bb53209201df4bf1904a51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 06:28:50 -0400 Subject: [PATCH 0378/1215] chore(deps): bump ash from 3.0.0-rc.28 to 3.0.0-rc.29 (#245) --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 5ffb4687..6060f120 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.28", "f42c8ef8d8485fb55781e47e7b00fcd3206e40b87f3656fdd87ebbba77b4ce62", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3f3b5407970167306fa97bf86c020722f54b28c0b01117c748808f47747723ae"}, + "ash": {:hex, :ash, "3.0.0-rc.29", "d62ff46bad2d2bd47bc2c47df88ee0297b8335c4583631f109e5af3f2de6b40e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fa2c508f4fd8ca9cdf10a4c2e101608801a06aed10d6074481e68248d87abd37"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.11", "d2e8ef8bd159a61226ae91af029d6abe126bd5f4abca353d92a6320e90d797f9", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ad8f70886668b1b239b082e2efeae93a5cb3d98677297accdf7d408d4c101f4"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -32,7 +32,7 @@ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"}, "spark": {:hex, :spark, "2.1.20", "204db8fd28378783c28a9dcb0bebdaf1d51b14a9ea106e1080457d29510a66ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e7a4f8f8ca7a477918af1eb65e20f2015f783a9a23e5f73d1020edf5b2ef69be"}, - "splode": {:hex, :splode, "0.2.2", "cda6709f829e3fe39a9550e8c8bc11821f994ecd660b5a0d60452770f227b9ca", [:mix], [], "hexpm", "8e02f47fac4bff7cfd29a65611ee3ab728dcc9c70a5c2e438addb8f25713265a"}, + "splode": {:hex, :splode, "0.2.3", "43a851790699c0993787d92bff017eb36f33ad6544974e47f7643f24ff89ac80", [:mix], [], "hexpm", "c91dc334647b5af4dc65b304635372df3d24c55bc389f9390cbb69d1c5bfd3e0"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, From 731a9faad357217e1afca03f3b2aefa2dd9f9d79 Mon Sep 17 00:00:00 2001 From: Albin Kocheril Chacko <8876252+albinkc@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:26:56 +0530 Subject: [PATCH 0379/1215] docs: align ash.codegen params with current version (#246) --- documentation/tutorials/get-started-with-postgres.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/tutorials/get-started-with-postgres.md b/documentation/tutorials/get-started-with-postgres.md index 58f00b4c..a8235e9b 100644 --- a/documentation/tutorials/get-started-with-postgres.md +++ b/documentation/tutorials/get-started-with-postgres.md @@ -190,7 +190,7 @@ First, we'll create the database with `mix ash.setup`. Then we will generate database migrations. This is one of the many ways that AshPostgres can save time and reduce complexity. ```bash -mix ash.codegen --name add_tickets_and_representatives +mix ash.codegen add_tickets_and_representatives ``` If you are unfamiliar with database migrations, it is a good idea to get a rough idea of what they are and how they work. See the links at the bottom of this guide for more. A rough overview of how migrations work is that each time you need to make changes to your database, they are saved as small, reproducible scripts that can be applied in order. This is necessary both for clean deploys as well as working with multiple developers making changes to the structure of a single database. From 1524501236d998cca39fdcbe9f9a43db1bfd4c62 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 24 Apr 2024 10:16:52 -0400 Subject: [PATCH 0380/1215] fix: properly honor `limit` in bulk operations --- lib/data_layer.ex | 149 ++++++++++++++++++++++---------------- test/bulk_update_test.exs | 17 +++++ 2 files changed, 104 insertions(+), 62 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 03372acd..64ec603b 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1237,7 +1237,13 @@ defmodule AshPostgres.DataLayer do |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> ecto_changeset(changeset, :update, true) - case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do + case bulk_updatable_query( + query, + resource, + changeset.atomics, + options[:calculations] || [], + changeset.context + ) do {:error, error} -> {:error, error} @@ -1276,9 +1282,13 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> query = if options[:return_records?] do + {:ok, query} = + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + |> add_calculations(options[:calculations] || [], resource) + query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([row], row) else Ecto.Query.exclude(query, :select) end @@ -1293,7 +1303,7 @@ defmodule AshPostgres.DataLayer do end) if options[:return_records?] do - {:ok, results} + {:ok, remap_mapped_fields(results, query)} else :ok end @@ -1308,8 +1318,8 @@ defmodule AshPostgres.DataLayer do end end - defp bulk_updatable_query(query, resource, atomics, context) do - Enum.reduce_while(atomics, {:ok, query}, fn {_, expr}, {:ok, query} -> + defp bulk_updatable_query(query, resource, atomics, calculations, context) do + Enum.reduce_while(atomics ++ calculations, {:ok, query}, fn {_, expr}, {:ok, query} -> used_aggregates = Ash.Filter.used_aggregates(expr, []) @@ -1332,63 +1342,76 @@ defmodule AshPostgres.DataLayer do end) |> case do {:ok, query} -> - case Enum.at(query.joins, 0) do - nil -> - {:ok, - query - |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation, context) - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by)} + needs_to_join? = + case Enum.at(query.joins, 0) do + nil -> + query.limit || query.offset - %{qual: :inner} -> - {:ok, - query - |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation, context) - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by)} - - _other_type_of_join -> - root_query = - from(row in query.from.source, []) - |> Map.put(:__ash_bindings__, query.__ash_bindings__) - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - - dynamic = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row, joining], - field(row, ^pkey) == field(joining, ^pkey) and ^dynamic - ) - else - Ecto.Query.dynamic([row, joining], field(row, ^pkey) == field(joining, ^pkey)) - end - end) + %{qual: :inner} -> + query.limit || query.offset - faked_query = - from(row in root_query, - inner_join: limiter in ^root_query, - as: ^0, - on: ^dynamic - ) + _other_type_of_join -> + true + end + + if needs_to_join? do + root_query = + from(row in query.from.source, []) + |> Map.put(:__ash_bindings__, query.__ash_bindings__) + |> Ecto.Query.exclude(:select) + |> Map.put(:limit, query.limit) + |> Map.put(:offset, query.offset) + + root_query = + if query.limit || query.offset do + root_query + else + Ecto.Query.exclude(root_query, :order_by) + end - joins_to_add = - for {%{on: on} = join, ix} <- Enum.with_index(query.joins) do - %{join | on: Ecto.Query.Planner.rewrite_sources(on, &(&1 + 1)), ix: ix + 1} + dynamic = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row, joining], + field(row, ^pkey) == field(joining, ^pkey) and ^dynamic + ) + else + Ecto.Query.dynamic([row, joining], field(row, ^pkey) == field(joining, ^pkey)) end + end) - {:ok, - %{ - faked_query - | joins: faked_query.joins ++ joins_to_add, - aliases: Map.new(query.aliases, fn {key, val} -> {key, val + 1} end), - wheres: - faked_query.wheres ++ - Enum.map(query.wheres, fn where -> - Ecto.Query.Planner.rewrite_sources(where, &(&1 + 1)) - end) - }} + faked_query = + from(row in root_query, + inner_join: limiter in ^subquery(root_query), + as: ^0, + on: ^dynamic + ) + + joins_to_add = + for {%{on: on} = join, ix} <- Enum.with_index(query.joins) do + %{join | on: Ecto.Query.Planner.rewrite_sources(on, &(&1 + 1)), ix: ix + 1} + end + + {:ok, + %{ + faked_query + | joins: faked_query.joins ++ joins_to_add, + aliases: Map.new(query.aliases, fn {key, val} -> {key, val + 1} end), + limit: nil, + offset: nil, + wheres: + faked_query.wheres ++ + Enum.map(query.wheres, fn where -> + Ecto.Query.Planner.rewrite_sources(where, &(&1 + 1)) + end) + }} + else + {:ok, + query + |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation, context) + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by)} end {:error, error} -> @@ -1441,7 +1464,7 @@ defmodule AshPostgres.DataLayer do query = if Enum.any?(query.joins, &(&1.qual != :inner)) do - root_query = + {:ok, root_query} = from(row in query.from.source, []) |> AshSql.Bindings.default_bindings( resource, @@ -1450,6 +1473,7 @@ defmodule AshPostgres.DataLayer do ) |> Ecto.Query.exclude(:select) |> Ecto.Query.exclude(:order_by) + |> add_calculations(options[:calculations] || [], resource) on = Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> @@ -1466,6 +1490,7 @@ defmodule AshPostgres.DataLayer do from(row in root_query, select: row, join: subquery(query), + as: :sub, on: ^on ) else @@ -1476,7 +1501,7 @@ defmodule AshPostgres.DataLayer do if options[:return_records?] do query |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([row], row) + |> Ecto.Query.select([sub: sub], sub) else query |> Ecto.Query.exclude(:select) @@ -1491,7 +1516,7 @@ defmodule AshPostgres.DataLayer do end) if options[:return_records?] do - {:ok, results} + {:ok, remap_mapped_fields(results, query)} else :ok end @@ -2446,7 +2471,7 @@ defmodule AshPostgres.DataLayer do select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) - case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do + case bulk_updatable_query(query, resource, changeset.atomics, [], changeset.context) do {:error, error} -> {:error, error} diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index e2650df2..6ddbc6d4 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -61,6 +61,23 @@ defmodule AshPostgres.BulkUpdateTest do assert titles == ["fred_stuff", "george"] end + test "bulk updates can be limited" do + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.Query.limit(1) + |> Ash.Query.sort(:title) + |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) + + titles = + Post + |> Ash.read!() + |> Enum.map(& &1.title) + |> Enum.sort() + + assert titles == ["fred_stuff", "george"] + end + test "the query can join to related tables when necessary" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) From 0fedb25de0e66ac6ef6a4488f8b106270f80720d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 24 Apr 2024 11:50:45 -0400 Subject: [PATCH 0381/1215] chore: release version v2.0.0-rc.11 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a53f477..73ee1b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.11](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2024-04-24) + + + + +### Bug Fixes: + +* properly honor `limit` in bulk operations + ## [v2.0.0-rc.10](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2024-04-23) diff --git a/mix.exs b/mix.exs index 3ce7a64a..4ef7b3b9 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.10" + @version "2.0.0-rc.11" def project do [ From 49548bb964fe60364de0f2dcbe60d4b65286e66e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 26 Apr 2024 03:23:31 -0400 Subject: [PATCH 0382/1215] chore: update ash_sql --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 4ef7b3b9..df5dabce 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.11")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.12")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 6060f120..5df4cd70 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.29", "d62ff46bad2d2bd47bc2c47df88ee0297b8335c4583631f109e5af3f2de6b40e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fa2c508f4fd8ca9cdf10a4c2e101608801a06aed10d6074481e68248d87abd37"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.11", "d2e8ef8bd159a61226ae91af029d6abe126bd5f4abca353d92a6320e90d797f9", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ad8f70886668b1b239b082e2efeae93a5cb3d98677297accdf7d408d4c101f4"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.12", "03afc406a7c768e5fda9e1ab0e6f1a890861aaf593397db35baf9d240986b188", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5eb5ad6094bf8c6932d8d02fe2598005a6986f1478e996f1eb9ade6fe8a6a60c"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 4bad5990c87684c75b7e4f1d42884becd125a286 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Sat, 27 Apr 2024 01:32:42 +0200 Subject: [PATCH 0383/1215] fix: fix argument order in AshSql.Bindings.default_bindings/4 (#251) --- lib/data_layer.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 64ec603b..ed91f458 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -692,7 +692,7 @@ defmodule AshPostgres.DataLayer do @impl true def run_query(query, resource) do - query = AshSql.Bindings.default_bindings(query, AshPostgres.SqlImplementation, resource) + query = AshSql.Bindings.default_bindings(query, resource, AshPostgres.SqlImplementation) if AshPostgres.DataLayer.Info.polymorphic?(resource) && no_table?(query) do raise_table_error!(resource, :read) @@ -793,8 +793,8 @@ defmodule AshPostgres.DataLayer do subquery = AshSql.Bindings.default_bindings( subquery, - AshPostgres.SqlImplementation, - source_resource + source_resource, + AshPostgres.SqlImplementation ) {global_filter, can_group} = From a2083ff7366762cfafe7cb47380cd8500e0b4473 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 26 Apr 2024 23:51:29 -0400 Subject: [PATCH 0384/1215] fix: update ash_sql for inner join fixes closes #252 chore: small test fixes --- mix.exs | 2 +- mix.lock | 2 +- test/bulk_update_test.exs | 5 +++-- test/filter_test.exs | 25 +++++++++++++++++++++++++ test/support/resources/post.ex | 1 + 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index df5dabce..7ad73b3d 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.12")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.13")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 5df4cd70..a7a0b2fa 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.29", "d62ff46bad2d2bd47bc2c47df88ee0297b8335c4583631f109e5af3f2de6b40e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fa2c508f4fd8ca9cdf10a4c2e101608801a06aed10d6074481e68248d87abd37"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.12", "03afc406a7c768e5fda9e1ab0e6f1a890861aaf593397db35baf9d240986b188", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5eb5ad6094bf8c6932d8d02fe2598005a6986f1478e996f1eb9ade6fe8a6a60c"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.13", "7a0cde8b8a3535d3a49bcd8ab441a54eac7a2192dc9d64502df8a3a68a462a42", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d45d8187f8b7d86a67448a42b5ea445cf58db484f1b2a3363636c6461048ebdf"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 6ddbc6d4..c192bfd8 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -119,12 +119,13 @@ defmodule AshPostgres.BulkUpdateTest do assert_raise Ash.Error.Invalid, ~r/had no matching bulk strategy that could be used/, fn -> Post - |> Ash.Query.for_read(:paginated, authorize?: true) + |> Ash.Query.for_read(:paginated, %{}, authorize?: true) |> Ash.bulk_update!(:requires_initial_data, %{}, authorize?: true, allow_stream_with: :full_read, authorize_query?: false, - return_errors?: true + return_errors?: true, + return_records?: true ) end end diff --git a/test/filter_test.exs b/test/filter_test.exs index a7211bbc..a9db96d9 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,4 +1,5 @@ defmodule AshPostgres.FilterTest do + alias AshPostgres.Test.Organization use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Author, Comment, Post} alias AshPostgres.Test.ComplexCalculations.{Channel, ChannelMember} @@ -1017,4 +1018,28 @@ defmodule AshPostgres.FilterTest do |> Ash.read!() |> length == 1 end + + test "using `(is_nil(relationship) and other_relation_filter)` will trigger left join" do + organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "foo"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) + |> Ash.create!() + + assert [_] = + Post + |> Ash.Query.filter( + # it isn't smart enough to know we can left join here + # and there isn't currently a way to hint that it can + is_nil(author) and + contains( + fragment("lower(?)", organization.name), + fragment("lower(?)", "foo") + ) + ) + |> Ash.read!() + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c2276dc2..c5cac817 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -37,6 +37,7 @@ defmodule AshPostgres.Test.Post do end policy action_type(:update) do + authorize_if(action(:requires_initial_data)) authorize_if(relates_to_actor_via([:author, :authors_with_same_first_name])) authorize_unless(changing_attributes(title: [from: "good", to: "bad"])) end From ba8465e0376abaca011b9546d9de08e02b57da7e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 26 Apr 2024 23:53:43 -0400 Subject: [PATCH 0385/1215] chore: release version v2.0.0-rc.12 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73ee1b04..c14035e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.12](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2024-04-27) + + + + +### Bug Fixes: + +* update ash_sql for inner join fixes + +* fix argument order in AshSql.Bindings.default_bindings/4 (#251) + ## [v2.0.0-rc.11](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2024-04-24) diff --git a/mix.exs b/mix.exs index 7ad73b3d..728a2f56 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.11" + @version "2.0.0-rc.12" def project do [ From 5de491dc1b69a917ef17d2395678df524a6e3bf2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 26 Apr 2024 23:55:01 -0400 Subject: [PATCH 0386/1215] chore: update ash --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index a7a0b2fa..4341fb94 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.29", "d62ff46bad2d2bd47bc2c47df88ee0297b8335c4583631f109e5af3f2de6b40e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fa2c508f4fd8ca9cdf10a4c2e101608801a06aed10d6074481e68248d87abd37"}, + "ash": {:hex, :ash, "3.0.0-rc.32", "1797fc4fb7d116210d95c67e1c054d609f1dadd5e1c1da0f6b4a0f7e3935891f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9dd2399b35c9a47fc7f06afc86e2c2b89dd85621afd998d69ed411267c99674a"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.13", "7a0cde8b8a3535d3a49bcd8ab441a54eac7a2192dc9d64502df8a3a68a462a42", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d45d8187f8b7d86a67448a42b5ea445cf58db484f1b2a3363636c6461048ebdf"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 5cfba7c6fa6f29a5311d7bdc052f97a97ba791c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 23:56:55 -0400 Subject: [PATCH 0387/1215] chore(deps): bump ash from 3.0.0-rc.29 to 3.0.0-rc.32 (#253) Bumps [ash](https://github.com/ash-project/ash) from 3.0.0-rc.29 to 3.0.0-rc.32. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.0.0-rc.29...v3.0.0-rc.32) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From c8d9dfa82617c97378171b6c8cdc3625ee95d8a4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Apr 2024 09:44:13 -0400 Subject: [PATCH 0388/1215] fix: only reference `sub` if a subquery is created --- lib/data_layer.ex | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ed91f458..638a86c5 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1499,9 +1499,15 @@ defmodule AshPostgres.DataLayer do query = if options[:return_records?] do - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([sub: sub], sub) + if Enum.any?(query.joins, &(&1.qual != :inner)) do + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([sub: sub], sub) + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + end else query |> Ecto.Query.exclude(:select) From 203b4ca2879fcb0ea62b178036378004c2f6ebc5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Apr 2024 09:49:47 -0400 Subject: [PATCH 0389/1215] fix: ensure limit/offset triggers joining for update/destroy query --- lib/data_layer.ex | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 638a86c5..9f169dd4 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1343,16 +1343,7 @@ defmodule AshPostgres.DataLayer do |> case do {:ok, query} -> needs_to_join? = - case Enum.at(query.joins, 0) do - nil -> - query.limit || query.offset - - %{qual: :inner} -> - query.limit || query.offset - - _other_type_of_join -> - true - end + Enum.any?(query.joins, &(&1.qual != :inner)) || query.limit || query.offset if needs_to_join? do root_query = @@ -1462,8 +1453,11 @@ defmodule AshPostgres.DataLayer do changeset.resource ) + needs_to_join? = + Enum.any?(query.joins, &(&1.qual != :inner)) || query.limit || query.offset + query = - if Enum.any?(query.joins, &(&1.qual != :inner)) do + if needs_to_join? do {:ok, root_query} = from(row in query.from.source, []) |> AshSql.Bindings.default_bindings( From 76cfa7b5bb94e55ced6df9151cb7eef397914229 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Apr 2024 11:20:42 -0400 Subject: [PATCH 0390/1215] chore: release version v2.0.0-rc.13 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c14035e0..ea1d8635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.13](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.12...v2.0.0-rc.13) (2024-04-27) + + + + +### Bug Fixes: + +* ensure limit/offset triggers joining for update/destroy query + +* only reference `sub` if a subquery is created + ## [v2.0.0-rc.12](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2024-04-27) diff --git a/mix.exs b/mix.exs index 728a2f56..9d7f521b 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.12" + @version "2.0.0-rc.13" def project do [ From e1a9af2d873fa341ce9a455a0c208a73972bfb0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 07:01:37 -0400 Subject: [PATCH 0391/1215] chore(deps): bump ash from 3.0.0-rc.32 to 3.0.0-rc.36 (#254) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 4341fb94..d7efbf0e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.32", "1797fc4fb7d116210d95c67e1c054d609f1dadd5e1c1da0f6b4a0f7e3935891f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9dd2399b35c9a47fc7f06afc86e2c2b89dd85621afd998d69ed411267c99674a"}, + "ash": {:hex, :ash, "3.0.0-rc.36", "448b3d67b9c2e14d8ae05f61bc87a00ce9d2c87c9f8cd66b8f658e9ecb477d98", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5f8c5dd1ab071539d591679e1a57fea3a22f910365b7d2c789e8945eda9c4b7e"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.13", "7a0cde8b8a3535d3a49bcd8ab441a54eac7a2192dc9d64502df8a3a68a462a42", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d45d8187f8b7d86a67448a42b5ea445cf58db484f1b2a3363636c6461048ebdf"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 8ca1383b7b0fe66cee9aa87dfd009223e58eafcb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Apr 2024 17:04:24 -0400 Subject: [PATCH 0392/1215] improvement: support latest ash & calculate/3 capability --- lib/data_layer.ex | 42 ++++++++++++++++++++++++++++++++++++++++++ mix.exs | 3 ++- mix.lock | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 9f169dd4..73097c47 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -606,6 +606,7 @@ defmodule AshPostgres.DataLayer do def can?(_, :aggregate_filter), do: true def can?(_, :aggregate_sort), do: true + def can?(_, :calculate), do: true def can?(_, :expression_calculation), do: true def can?(_, :expression_calculation_sort), do: true def can?(_, :create), do: true @@ -1526,6 +1527,47 @@ defmodule AshPostgres.DataLayer do end end + @impl true + def calculate(resource, expressions, context) do + ash_query = + resource + |> Ash.Query.new() + |> Map.put(:context, context) + + {:ok, query} = Ash.Query.data_layer_query(ash_query) + + query = + AshSql.Bindings.default_bindings(query, resource, AshPostgres.SqlImplementation) + + try do + {dynamics, query} = + Enum.reduce(expressions, {[], query}, fn expression, {dynamics, query} -> + {dynamic, acc} = AshSql.Expr.dynamic_expr(query, expression, query.__ash_bindings__) + {[dynamic | dynamics], AshSql.Bindings.merge_expr_accumulator(query, acc)} + end) + + dynamics = + dynamics + |> Enum.with_index() + |> Map.new(fn {dynamic, index} -> {index, dynamic} end) + + query = + Ecto.Query.from(row in fragment("UNNEST(ARRAY[1])"), select: ^dynamics) + |> Map.put(:__ash_bindings__, query.__ash_bindings__) + + repo = + AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, ash_query) + + with_savepoint(repo, query, fn -> + {:ok, + repo.one(query) |> Enum.sort_by(&elem(&1, 0)) |> Enum.map(&elem(&1, 1)) |> Enum.reverse()} + end) + rescue + e -> + handle_raised_error(e, __STACKTRACE__, query, resource) + end + end + @impl true def bulk_create(resource, stream, options) do changesets = Enum.to_list(stream) diff --git a/mix.exs b/mix.exs index 9d7f521b..7e304ddf 100644 --- a/mix.exs +++ b/mix.exs @@ -17,6 +17,7 @@ defmodule AshPostgres.MixProject do deps: deps(), description: @description, elixirc_paths: elixirc_paths(Mix.env()), + consolidate_protocols: Mix.env() == :prod, preferred_cli_env: [ coveralls: :test, "coveralls.github": :test, @@ -172,7 +173,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0.0-rc")}, + {:ash, ash_version("~> 3.0.0-rc and >= 3.0.0-rc.38")}, {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.13")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index d7efbf0e..9ba6bb8c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.36", "448b3d67b9c2e14d8ae05f61bc87a00ce9d2c87c9f8cd66b8f658e9ecb477d98", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5f8c5dd1ab071539d591679e1a57fea3a22f910365b7d2c789e8945eda9c4b7e"}, + "ash": {:hex, :ash, "3.0.0-rc.38", "87a3ca7d18196e76723f485c553405373908d637e668eea5e2ed1a37c1aad1c8", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "16bc98312ef1ac7aca3112ee41504e32072bc665546702722414aec1f0f8e338"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.13", "7a0cde8b8a3535d3a49bcd8ab441a54eac7a2192dc9d64502df8a3a68a462a42", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d45d8187f8b7d86a67448a42b5ea445cf58db484f1b2a3363636c6461048ebdf"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 2c1ae04630d452035011d15c387d38b84a702b7b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 29 Apr 2024 17:05:45 -0400 Subject: [PATCH 0393/1215] chore: release version v2.0.0-rc.14 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1d8635..24351f4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.14](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-04-29) + + + + +### Improvements: + +* support latest ash & calculate/3 capability + ## [v2.0.0-rc.13](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.12...v2.0.0-rc.13) (2024-04-27) diff --git a/mix.exs b/mix.exs index 7e304ddf..375935f8 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule AshPostgres.MixProject do support, and delegates to a configured repo. """ - @version "2.0.0-rc.13" + @version "2.0.0-rc.14" def project do [ From 38b6c9252693250da337025943f56927317173b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:43:16 -0400 Subject: [PATCH 0394/1215] chore(deps): bump ash_sql from 0.1.1-rc.13 to 0.1.1-rc.15 (#255) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 9ba6bb8c..a579ecfc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.38", "87a3ca7d18196e76723f485c553405373908d637e668eea5e2ed1a37c1aad1c8", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "16bc98312ef1ac7aca3112ee41504e32072bc665546702722414aec1f0f8e338"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.13", "7a0cde8b8a3535d3a49bcd8ab441a54eac7a2192dc9d64502df8a3a68a462a42", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d45d8187f8b7d86a67448a42b5ea445cf58db484f1b2a3363636c6461048ebdf"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.15", "b6b67c193d807bec150e32f19669a2027ab262f1e46cd4f0624529d7a49379e1", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f3c297c285d53a51d6b3d66668535ba664e749d8165e2cbc6a3e4701a2252f10"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 098a8f845e8627a923a5c3b5947e7af4ac3e6563 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 07:15:43 -0400 Subject: [PATCH 0395/1215] chore(deps): bump ash from 3.0.0-rc.38 to 3.0.0-rc.39 (#257) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index a579ecfc..231ccab6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.38", "87a3ca7d18196e76723f485c553405373908d637e668eea5e2ed1a37c1aad1c8", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "16bc98312ef1ac7aca3112ee41504e32072bc665546702722414aec1f0f8e338"}, + "ash": {:hex, :ash, "3.0.0-rc.39", "de0d8e768ff4da10710239e5faa6591d2e6144ef796cc3dca8a568968b4dc9cf", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "054dc4c2be414e8b07f958fb0340a6ead3359622f24e819cb1909459f4d7b8a8"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.15", "b6b67c193d807bec150e32f19669a2027ab262f1e46cd4f0624529d7a49379e1", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f3c297c285d53a51d6b3d66668535ba664e749d8165e2cbc6a3e4701a2252f10"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From a6c1dedffb495bb6924ca2f13b2ceca531476380 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 1 May 2024 08:27:14 -0400 Subject: [PATCH 0396/1215] test: add tests for fix in ash_sql around actor refs in relationships --- mix.exs | 2 +- mix.lock | 2 +- test/filter_test.exs | 28 ++++++++++++++++++++++++++++ test/support/resources/post.ex | 6 ++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 375935f8..0cbb1f93 100644 --- a/mix.exs +++ b/mix.exs @@ -174,7 +174,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc and >= 3.0.0-rc.38")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.13")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.16")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 231ccab6..88523ca2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.39", "de0d8e768ff4da10710239e5faa6591d2e6144ef796cc3dca8a568968b4dc9cf", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "054dc4c2be414e8b07f958fb0340a6ead3359622f24e819cb1909459f4d7b8a8"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.15", "b6b67c193d807bec150e32f19669a2027ab262f1e46cd4f0624529d7a49379e1", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f3c297c285d53a51d6b3d66668535ba664e749d8165e2cbc6a3e4701a2252f10"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.16", "fa36a7e8718220e0cb1e25fe6e9b4a862ac3663e8095a2e48e5a53e5bc7426b5", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "604903f96df8bd5472e995e32743c5dd458f715445bbffe5b2ad6223e08847ea"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/filter_test.exs b/test/filter_test.exs index a9db96d9..4a9c4442 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -325,6 +325,34 @@ defmodule AshPostgres.FilterTest do end end + describe "using actor in filters" do + test "actor templates work in relationships" do + author = + Author + |> Ash.Changeset.for_create(:create, %{badges: [:author_of_the_year]}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{badges: [:author_of_the_year]}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "match", author_id: author.id}) + |> Ash.create!() + + assert [_] = + Post + |> Ash.Query.filter(not is_nil(current_user_author.id)) + |> Ash.read!(actor: author, authorize?: false) + + assert [] = + Post + |> Ash.Query.filter(not is_nil(current_user_author.id)) + |> Ash.read!(actor: author2, authorize?: false) + end + end + describe "accessing embeds" do setup do Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c5cac817..b31531c0 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -219,6 +219,12 @@ defmodule AshPostgres.Test.Post do attribute_writable?(true) end + belongs_to(:current_user_author, AshPostgres.Test.Author) do + source_attribute(:author_id) + define_attribute?(false) + filter(expr(^actor(:id) == id)) + end + belongs_to(:author, AshPostgres.Test.Author) do public?(true) end From c26fdf358f97aec7bec463225feae54d5470464e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 1 May 2024 14:40:16 -0400 Subject: [PATCH 0397/1215] chore: update documentation with new standards --- README.md | 65 +++++++------------ config/config.exs | 5 +- documentation/home.md | 32 --------- .../what-is-ash-postgres.md | 34 ++++++++++ ...es.md => get-started-with-ash-postgres.md} | 0 lib/ash_postgres.ex | 2 +- mix.exs | 34 ++++------ 7 files changed, 73 insertions(+), 99 deletions(-) delete mode 100644 documentation/home.md create mode 100644 documentation/topics/about-ash-postgres/what-is-ash-postgres.md rename documentation/tutorials/{get-started-with-postgres.md => get-started-with-ash-postgres.md} (100%) diff --git a/README.md b/README.md index 3999424d..0d6cd983 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,40 @@ -# AshPostgres +![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-black-text.png?raw=true#gh-light-mode-only) +![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-white-text.png?raw=true#gh-dark-mode-only) -![Elixir CI](https://github.com/ash-project/ash_postgres/workflows/Elixir%20CI/badge.svg) +![Elixir CI](https://github.com/ash-project/ash_postgres/workflows/CI/badge.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Coverage Status](https://coveralls.io/repos/github/ash-project/ash_postgres/badge.svg?branch=main)](https://coveralls.io/github/ash-project/ash_postgres?branch=main) [![Hex version badge](https://img.shields.io/hexpm/v/ash_postgres.svg)](https://hex.pm/packages/ash_postgres) +[![Hexdocs badge](https://img.shields.io/badge/docs-hexdocs-purple)](https://hexdocs.pm/ash_postgres) -AshPostgres supports all the capabilities of an Ash data layer. AshPostgres is the primary Ash data layer. - -Custom Predicates: - -- `AshPostgres.Predicates.Trigram` - -## DSL - -See the DSL documentation in `AshPostgres.DataLayer` for DSL documentation - -## Usage +# AshPostgres -Add `ash_postgres` to your `mix.exs` file. +Welcome! This documentation is for `AshPostgres`, the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). -```elixir -{:ash_postgres, "~> 1.3.6"} -``` +## Tutorials -To use this data layer, you need to chage your Ecto Repo's from `use Ecto.Repo`, -to `use AshPostgres.Repo`. because AshPostgres adds functionality to Ecto Repos. +- [Get Started](documentation/tutorials/get-started-with-ash-postgres.md) -Then, configure each of your `Ash.Resource` resources by adding `use Ash.Resource, data_layer: AshPostgres.DataLayer` like so: +## Topics -```elixir -defmodule MyApp.SomeResource do - use Ash.Resource, domain: MyDomain, data_layer: AshPostgres.DataLayer +- [What is AshPostgres?](documentation/topics/about_ash_postgres/what-is-ash-postgres.md) - postgres do - repo MyApp.Repo - table "table_name" - end +### Resources - attributes do - # ... Attribute definitions - end -end -``` +- [References](documentation/topics/resources/references.md) +- [Polymorphic Resources](documentation/topics/resources/polymorphic-resources.md) -## Generating Migrations +### Development -See the documentation for `Mix.Tasks.AshPostgres.GenerateMigrations` for how to generate -migrations from your resources +- [Migrations and tasks](documentation/topics/development/migrations-and-tasks.md) +- [Testing](documentation/topics/development/testing.md) +- [Upgrading to 2.0](documentation/topics/development/upgrading-to-2.0.md) -# Contributors +### Advanced -Ash is made possible by its excellent community! +- [Expressions](documentation/topics/advanced/expressions.md) +- [Manual Relationships](documentation/topics/advanced/manual-relationships.md) +- [Schema Based Multitenancy](documentation/topics/advanced/schema-based-multitenancy.md) - - - +## Reference -[Become a contributor](https://ash-hq.org/docs/guides/ash/latest/how_to/contribute.md) +- [AshPostgres.DataLayer DSL](documentation/dsls/DSL:-AshPostgres.DataLayer.md) diff --git a/config/config.exs b/config/config.exs index 10ce748c..c9494788 100644 --- a/config/config.exs +++ b/config/config.exs @@ -10,7 +10,10 @@ if Mix.env() == :dev do manage_mix_version?: true, # Instructs the tool to manage the version in your README.md # Pass in `true` to use `"README.md"` or a string to customize - manage_readme_version: ["README.md", "documentation/tutorials/get-started-with-postgres.md"], + manage_readme_version: [ + "README.md", + "documentation/tutorials/get-started-with-ash-postgres.md" + ], version_tag_prefix: "v" end diff --git a/documentation/home.md b/documentation/home.md deleted file mode 100644 index 40d1f66e..00000000 --- a/documentation/home.md +++ /dev/null @@ -1,32 +0,0 @@ -# AshPostgres Documentation - -Welcome! This documentation is for `AshPostgres`, the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). If you have not yet, please see the [Ash Framework documentation](https://hexdocs.pm/ash). - -## Dive In - -- [Get Started](documentation/tutorials/get-started-with-postgres.md) - ---- - -## Tutorials - -- [Get Started](documentation/tutorials/get-started-with-postgres.md) - -## Topics - -### Resources - -- [References](documentation/topics/resources/references.md) -- [Polymorphic Resources](documentation/topics/resources/polymorphic-resources.md) - -### Development - -- [Migrations and tasks](documentation/topics/development/migrations-and-tasks.md) -- [Testing](documentation/topics/development/testing.md) -- [Upgrading to 2.0](documentation/topics/development/upgrading-to-2.0.md) - -### Advanced - -- [Expressions](documentation/topics/advanced/expressions.md) -- [Manual Relationships](documentation/topics/advanced/manual-relationships.md) -- [Schema Based Multitenancy](documentation/topics/advanced/schema-based-multitenancy.md) diff --git a/documentation/topics/about-ash-postgres/what-is-ash-postgres.md b/documentation/topics/about-ash-postgres/what-is-ash-postgres.md new file mode 100644 index 00000000..253c4df3 --- /dev/null +++ b/documentation/topics/about-ash-postgres/what-is-ash-postgres.md @@ -0,0 +1,34 @@ +# What is AshPostgres? + +AshPostgres is the PostgreSQL `Ash.DataLayer` for [Ash Framework](https://hexdocs.pm/ash). This is the most fully-featured Ash data layer, and unless you need a specific characteristic or feature of another data layer, you should use `AshPostgres`. + +Use this to persist records in a PostgreSQL table or view. For example, the resource below would be persisted in a table called `tweets`: + +```elixir +defmodule MyApp.Tweet do + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + integer_primary_key :id + attribute :text, :string + end + + relationships do + belongs_to :author, MyApp.User + end + + postgres do + table "tweets" + repo MyApp.Repo + end +end +``` + +The table might look like this: + +| id | text | author_id | +| --- | --------------- | --------- | +| 1 | "Hello, world!" | 1 | + +Creating records would add to the table, destroying records would remove from the table, and updating records would update the table. diff --git a/documentation/tutorials/get-started-with-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md similarity index 100% rename from documentation/tutorials/get-started-with-postgres.md rename to documentation/tutorials/get-started-with-ash-postgres.md diff --git a/lib/ash_postgres.ex b/lib/ash_postgres.ex index c301751e..d26103e4 100644 --- a/lib/ash_postgres.ex +++ b/lib/ash_postgres.ex @@ -2,7 +2,7 @@ defmodule AshPostgres do @moduledoc """ The AshPostgres extension gives you tools to map a resource to a postgres database table. - For more, check out the [getting started guide](/documentation/tutorials/get-started-with-postgres.md) + For more, check out the [getting started guide](/documentation/tutorials/get-started-with-ash-postgres.md) """ @deprecated "use AshPostgres.DataLayer.Info.repo/1" diff --git a/mix.exs b/mix.exs index 0cbb1f93..d19848f8 100644 --- a/mix.exs +++ b/mix.exs @@ -67,7 +67,7 @@ defmodule AshPostgres.MixProject do defp docs do [ - main: "home", + main: "readme", source_ref: "v#{@version}", logo: "logos/small-logo.png", before_closing_head_tag: fn type -> @@ -86,8 +86,9 @@ defmodule AshPostgres.MixProject do end end, extras: [ - {"documentation/home.md", title: "Home"}, - "documentation/tutorials/get-started-with-postgres.md", + {"README.md", title: "Home"}, + "documentation/tutorials/get-started-with-ash-postgres.md", + "documentation/topics/about-ash-postgres/what-is-ash-postgres.md", "documentation/topics/resources/references.md", "documentation/topics/resources/polymorphic-resources.md", "documentation/topics/development/migrations-and-tasks.md", @@ -100,26 +101,13 @@ defmodule AshPostgres.MixProject do "CHANGELOG.md" ], groups_for_extras: [ - Tutorials: [ - "documentation/tutorials/get-started-with-postgres.md" - ], - Resources: [ - "documentation/topics/resources/references.md", - "documentation/topics/resources/polymorphic-resources.md" - ], - Development: [ - "documentation/topics/development/migrations-and-tasks.md", - "documentation/topics/development/testing.md", - "documentation/topics/development/upgrading-to-2.0.md" - ], - Advanced: [ - "documentation/topics/advanced/expressions.md", - "documentation/topics/advanced/schema-based-multitenancy.md", - "documentation/topics/advanced/manual-relationships.md" - ], - Reference: [ - "documentation/dsls/DSL:-AshPostgres.DataLayer.md" - ] + Tutorials: ~r"documentation/tutorials", + Resources: ~r"documentation/topics", + Development: ~r"documentation/topics/development", + "About AshPostgres": ["CHANGELOG.md"], + Advanced: ~r"documentation/topics/advanced", + Reference: ~r"documentation/topics/dsls", + DSLs: ~r"documentation/dsls" ], skip_undefined_reference_warnings_on: [ "CHANGELOG.md", From 9c6002750695f154357d9e2aa81f38efdb4293b0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 1 May 2024 14:45:42 -0400 Subject: [PATCH 0398/1215] chore: remove unnecessary --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d6cd983..21fccca1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ # AshPostgres -Welcome! This documentation is for `AshPostgres`, the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). +Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). ## Tutorials From a4bd02535fe32f797a1d58b99e1fa11f40bdb6ac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 1 May 2024 15:22:28 -0400 Subject: [PATCH 0399/1215] chore: update package description --- mix.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index d19848f8..d83fd472 100644 --- a/mix.exs +++ b/mix.exs @@ -2,8 +2,7 @@ defmodule AshPostgres.MixProject do use Mix.Project @description """ - A postgres data layer for `Ash` resources. Leverages Ecto's postgres - support, and delegates to a configured repo. + The PostgreSQL data layer for Ash Framework """ @version "2.0.0-rc.14" From 8beacf0b389de5b08279b64817899ff2c45c72ee Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 1 May 2024 16:23:51 -0400 Subject: [PATCH 0400/1215] chore: fix link to ash_postgres --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21fccca1..2d9bb79d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https:// ## Topics -- [What is AshPostgres?](documentation/topics/about_ash_postgres/what-is-ash-postgres.md) +- [What is AshPostgres?](documentation/topics/about-ash-postgres/what-is-ash-postgres.md) ### Resources From 3e95715be821609d7f55a260bbc026fc0fa01e4b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 2 May 2024 01:40:32 -0400 Subject: [PATCH 0401/1215] chore: update ash_sql for bug fix --- mix.exs | 2 +- mix.lock | 6 +++--- test/manual_relationships_test.exs | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index d83fd472..7ef5dca0 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc and >= 3.0.0-rc.38")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.16")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.17")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 88523ca2..172d1551 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.39", "de0d8e768ff4da10710239e5faa6591d2e6144ef796cc3dca8a568968b4dc9cf", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "054dc4c2be414e8b07f958fb0340a6ead3359622f24e819cb1909459f4d7b8a8"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.16", "fa36a7e8718220e0cb1e25fe6e9b4a862ac3663e8095a2e48e5a53e5bc7426b5", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "604903f96df8bd5472e995e32743c5dd458f715445bbffe5b2ad6223e08847ea"}, + "ash": {:hex, :ash, "3.0.0-rc.40", "cc3951779e531c3e736e6ec5767f1887c25a4a015d2e6b7a6127775b8678654e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e9eff8857226f2859ecdca7bc167a231cdebded40b4c210fc0e30ba9d761243"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.17", "b55c4a71ba0e4cdfef905bcdfa9ebb74c75e59dd1c1b53fcfa1a8d81e9c58def", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0eda1e606959d6fb85c665f7465397792f0c128761db8177f1683807020e5f67"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -32,7 +32,7 @@ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"}, "spark": {:hex, :spark, "2.1.20", "204db8fd28378783c28a9dcb0bebdaf1d51b14a9ea106e1080457d29510a66ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e7a4f8f8ca7a477918af1eb65e20f2015f783a9a23e5f73d1020edf5b2ef69be"}, - "splode": {:hex, :splode, "0.2.3", "43a851790699c0993787d92bff017eb36f33ad6544974e47f7643f24ff89ac80", [:mix], [], "hexpm", "c91dc334647b5af4dc65b304635372df3d24c55bc389f9390cbb69d1c5bfd3e0"}, + "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, diff --git a/test/manual_relationships_test.exs b/test/manual_relationships_test.exs index 0783c94b..c1921dde 100644 --- a/test/manual_relationships_test.exs +++ b/test/manual_relationships_test.exs @@ -15,6 +15,21 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do Ash.load!(post, :count_of_comments_containing_title) end + test "exists can be used" do + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() + + assert [] = + Post + |> Ash.Query.filter(exists(comments_containing_title, true)) + |> Ash.read!() + end + test "aggregates can be loaded with data" do post = Post From 65f93561d81b21108cf81a5a787e976aad93ae3d Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Fri, 3 May 2024 02:31:35 +0200 Subject: [PATCH 0402/1215] chore: add failing test for bulk actions and non-null violations (#260) - bulk_create returns a Postgrex error instead of Ash.Error.Changes.Required like the non-bulk create does - bulk_update with :stream strategy does the same - bulk_update with :atomic strategy doesn't trim the empty string and writes it to the database --- test/bulk_create_test.exs | 14 ++++++++++++- test/bulk_update_test.exs | 36 +++++++++++++++++++++++++++++++- test/support/resources/record.ex | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index b351c3d1..f4edee0b 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.BulkCreateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.Post + alias AshPostgres.Test.{Post, Record} describe "bulk creates" do test "bulk creates insert each input" do @@ -204,6 +204,18 @@ defmodule AshPostgres.BulkCreateTest do ) |> Enum.to_list() end + + test "handle allow_nil? false correctly" do + assert %{ + errors: [ + %Ash.Error.Invalid{errors: [%Ash.Error.Changes.Required{field: :full_name}]} + ] + } = + Ash.bulk_create([%{full_name: ""}], Record, :create, + return_records?: true, + return_errors?: true + ) + end end describe "database errors" do diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index c192bfd8..b8a953b3 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.BulkUpdateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.Post + alias AshPostgres.Test.{Post, Record} require Ash.Expr require Ash.Query @@ -129,4 +129,38 @@ defmodule AshPostgres.BulkUpdateTest do ) end end + + test "bulk updates return error for null value if allow_nil? false with strategy :stream" do + Ash.bulk_create!([%{full_name: "foo"}], Record, :create) + + assert %Ash.BulkResult{ + error_count: 1, + errors: [ + %Ash.Error.Invalid{errors: [%Ash.Error.Changes.Required{field: :full_name}]} + ] + } = + Ash.bulk_update(Record, :update, %{full_name: ""}, + strategy: :stream, + return_records?: true, + return_errors?: true, + authorize?: false + ) + end + + test "bulk updates return error for null value if allow_nil? false with strategy :atomic" do + Ash.bulk_create!([%{full_name: "foo"}], Record, :create) + + assert %Ash.BulkResult{ + error_count: 1, + errors: [ + %Ash.Error.Invalid{errors: [%Ash.Error.Changes.Required{field: :full_name}]} + ] + } = + Ash.bulk_update(Record, :update, %{full_name: ""}, + strategy: :atomic, + return_records?: true, + return_errors?: true, + authorize?: false + ) + end end diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index 19a5aa76..b98ef59e 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -34,6 +34,6 @@ defmodule AshPostgres.Test.Record do actions do default_accept(:*) - defaults([:create, :read]) + defaults([:create, :read, :update]) end end From 56f3a5eb2fdbbbcff21c96559f9f7e8754f39bab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 2 May 2024 21:29:21 -0400 Subject: [PATCH 0403/1215] fix: fix calculate when exprs aren't dynamics test: add test for datetimes --- lib/data_layer.ex | 10 + .../test_repo/posts/20240503012410.json | 352 ++++++++++++++++++ .../20240503012410_migrate_resources21.exs | 21 ++ test/bulk_update_test.exs | 14 + test/support/resources/post.ex | 1 + 5 files changed, 398 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20240503012410.json create mode 100644 priv/test_repo/migrations/20240503012410_migrate_resources21.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 73097c47..020ad370 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1543,6 +1543,16 @@ defmodule AshPostgres.DataLayer do {dynamics, query} = Enum.reduce(expressions, {[], query}, fn expression, {dynamics, query} -> {dynamic, acc} = AshSql.Expr.dynamic_expr(query, expression, query.__ash_bindings__) + + dynamic = + case dynamic do + %Ecto.Query.DynamicExpr{} -> + dynamic + + other -> + Ecto.Query.dynamic(^other) + end + {[dynamic | dynamics], AshSql.Bindings.merge_expr_accumulator(query, acc)} end) diff --git a/priv/resource_snapshots/test_repo/posts/20240503012410.json b/priv/resource_snapshots/test_repo/posts/20240503012410.json new file mode 100644 index 00000000..238c6218 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240503012410.json @@ -0,0 +1,352 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "utc_datetime_usec", + "source": "datetime", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "CAD4281EFCDF0328EEC1C473F0041F4DCD0DB6431C3DF44540EA67F4FA4511FE", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "where": null, + "unique": true, + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240503012410_migrate_resources21.exs b/priv/test_repo/migrations/20240503012410_migrate_resources21.exs new file mode 100644 index 00000000..70781d33 --- /dev/null +++ b/priv/test_repo/migrations/20240503012410_migrate_resources21.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources21 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(:posts) do + add(:datetime, :utc_datetime_usec) + end + end + + def down do + alter table(:posts) do + remove(:datetime) + end + end +end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index b8a953b3..26bbd65f 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -20,6 +20,20 @@ defmodule AshPostgres.BulkUpdateTest do assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff")) end + test "bulk updates can set datetimes" do + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + now = DateTime.utc_now() + + Ash.bulk_update!(Post, :update, %{datetime: now}) + + posts = Ash.read!(Post) + + assert Enum.all?(posts, fn post -> + DateTime.compare(post.datetime, now) == :eq + end) + end + test "a map can be given as input" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index b31531c0..33df353c 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -174,6 +174,7 @@ defmodule AshPostgres.Test.Post do source(:title_column) end + attribute(:datetime, :utc_datetime_usec, public?: true) attribute(:score, :integer, public?: true) attribute(:public, :boolean, public?: true) attribute(:category, :ci_string, public?: true) From 77b824e23d686634ea42d319619d162e35965a4d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 3 May 2024 00:02:04 -0400 Subject: [PATCH 0404/1215] fix: update ash & fix subquery sort references --- config/config.exs | 2 ++ lib/data_layer.ex | 7 ++++--- test/bulk_update_test.exs | 15 ++++++++++++--- test/multitenancy_test.exs | 13 +++++++------ 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/config/config.exs b/config/config.exs index c9494788..24a02437 100644 --- a/config/config.exs +++ b/config/config.exs @@ -21,6 +21,8 @@ if Mix.env() == :test do config :ash, :validate_domain_resource_inclusion?, false config :ash, :validate_domain_config_inclusion?, false + config :ash, :policies, show_policy_breakdowns?: true + config :ash_postgres, :ash_domains, [AshPostgres.Test.Domain] config :ash_postgres, AshPostgres.TestRepo, diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 020ad370..265b33d5 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1348,7 +1348,7 @@ defmodule AshPostgres.DataLayer do if needs_to_join? do root_query = - from(row in query.from.source, []) + from(row in query.from.source, as: ^0) |> Map.put(:__ash_bindings__, query.__ash_bindings__) |> Ecto.Query.exclude(:select) |> Map.put(:limit, query.limit) @@ -1356,7 +1356,7 @@ defmodule AshPostgres.DataLayer do root_query = if query.limit || query.offset do - root_query + Map.put(root_query, :order_bys, query.order_bys) else Ecto.Query.exclude(root_query, :order_by) end @@ -1374,11 +1374,12 @@ defmodule AshPostgres.DataLayer do end) faked_query = - from(row in root_query, + from(row in query.from.source, inner_join: limiter in ^subquery(root_query), as: ^0, on: ^dynamic ) + |> Map.put(:__ash_bindings__, query.__ash_bindings__) joins_to_add = for {%{on: on} = join, ix} <- Enum.with_index(query.joins) do diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 26bbd65f..951db235 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -21,11 +21,17 @@ defmodule AshPostgres.BulkUpdateTest do end test "bulk updates can set datetimes" do - Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Post + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "george"}) + |> Ash.create!() now = DateTime.utc_now() - Ash.bulk_update!(Post, :update, %{datetime: now}) + Ash.bulk_update!(Post, :update, %{datetime: now}, strategy: :atomic) posts = Ash.read!(Post) @@ -81,7 +87,10 @@ defmodule AshPostgres.BulkUpdateTest do Post |> Ash.Query.limit(1) |> Ash.Query.sort(:title) - |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) + |> Ash.bulk_update!(:update, %{}, + atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}, + strategy: :atomic + ) titles = Post diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 70cdbf7b..d38c37a6 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -38,12 +38,13 @@ defmodule AshPostgres.Test.MultitenancyTest do end test "context multitenancy works with policies", %{org1: org1} do - Post - |> Ash.Changeset.for_create(:create, %{name: "foo"}) - |> Ash.Changeset.set_tenant(tenant(org1)) - |> Ash.create!() - |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true) - |> Ash.Changeset.set_tenant(tenant(org1)) + post = + Post + |> Ash.Changeset.for_create(:create, %{name: "foo"}, tenant: tenant(org1)) + |> Ash.create!() + + post + |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true, tenant: tenant(org1)) |> Ash.update!() end From 1bf01f14a2ca36729198d300d47f127b167b5bdc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 3 May 2024 00:03:22 -0400 Subject: [PATCH 0405/1215] chore: update ash --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 172d1551..f2168b95 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.40", "cc3951779e531c3e736e6ec5767f1887c25a4a015d2e6b7a6127775b8678654e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e9eff8857226f2859ecdca7bc167a231cdebded40b4c210fc0e30ba9d761243"}, + "ash": {:hex, :ash, "3.0.0-rc.41", "761e40d12e83255ee23b194b73bb63e51bd4ddead3978722eece4942dfc86c4a", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68e86379ce96f1d89acbb987e44d1dcf9c3d84d44b7fac0500c57cf5bde2baae"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.17", "b55c4a71ba0e4cdfef905bcdfa9ebb74c75e59dd1c1b53fcfa1a8d81e9c58def", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0eda1e606959d6fb85c665f7465397792f0c128761db8177f1683807020e5f67"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From f405225731d13a4c71e6be45627ce1dd91185382 Mon Sep 17 00:00:00 2001 From: Lukas Ender Date: Fri, 3 May 2024 18:31:42 +0200 Subject: [PATCH 0406/1215] feat: test with calculations using `exists` (#240) --- test/calculation_test.exs | 26 ++++++++++++++++++++++++++ test/support/resources/post.ex | 4 ++++ 2 files changed, 30 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index bee98335..50bc3d78 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -107,6 +107,32 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + test "calculations evaluate `exists` as expected" do + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "Foo", + bio: %{title: "Mr.", bio: "Bones"} + }) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert [%{has_author: true, has_comments: true}] = + Post + |> Ash.Query.load([:has_author, :has_comments]) + |> Ash.read!() + end + test "calculations can refer to embedded attributes" do author = Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 33df353c..8aa119f1 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -308,6 +308,10 @@ defmodule AshPostgres.Test.Post do ) ) + calculate(:has_author, :boolean, expr(exists(author, true == true))) + + calculate(:has_comments, :boolean, expr(exists(comments, true == true))) + calculate( :has_no_followers, :boolean, From 8eef44951c9d5a6fa4bcf2a608550494f181d7dc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 3 May 2024 21:25:54 -0400 Subject: [PATCH 0407/1215] test additional bulk action tests --- lib/data_layer.ex | 1 + mix.lock | 2 +- test/bulk_update_test.exs | 19 +++++++++++++++++++ test/support/resources/post.ex | 6 +----- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 265b33d5..430a1f5f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2771,6 +2771,7 @@ defmodule AshPostgres.DataLayer do func = fn -> repo.on_transaction_begin(reason) + func.() end diff --git a/mix.lock b/mix.lock index f2168b95..95bed17c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.41", "761e40d12e83255ee23b194b73bb63e51bd4ddead3978722eece4942dfc86c4a", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68e86379ce96f1d89acbb987e44d1dcf9c3d84d44b7fac0500c57cf5bde2baae"}, + "ash": {:hex, :ash, "3.0.0-rc.43", "2154c2cd38773c7ff56f00f1161505da02139e4b851b7a0a14ef929dc12bbfbf", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d9b8a2ae389426ffd373c4cede7e977a906a87bf290938e948e53e25d73886e3"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.17", "b55c4a71ba0e4cdfef905bcdfa9ebb74c75e59dd1c1b53fcfa1a8d81e9c58def", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0eda1e606959d6fb85c665f7465397792f0c128761db8177f1683807020e5f67"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 951db235..9dc7389d 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -81,6 +81,25 @@ defmodule AshPostgres.BulkUpdateTest do assert titles == ["fred_stuff", "george"] end + test "errors in streaming bulk updates that would result in rollbacks are handled" do + Ash.bulk_create!( + [ + %{uniq_custom_one: "fred", uniq_custom_two: "weasley1"}, + %{uniq_custom_one: "fred", uniq_custom_two: "weasley2"} + ], + Post, + :create, + return_records?: true + ) + + assert %Ash.BulkResult{errors: [%Ash.Error.Invalid{}]} = + Post + |> Ash.bulk_update(:update, %{uniq_custom_two: "weasley"}, + strategy: :stream, + return_errors?: true + ) + end + test "bulk updates can be limited" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 8aa119f1..59b2e239 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -77,7 +77,7 @@ defmodule AshPostgres.Test.Post do actions do default_accept(:*) - defaults([:destroy]) + defaults([:read, :destroy]) destroy :destroy_with_confirm do require_atomic?(false) @@ -123,10 +123,6 @@ defmodule AshPostgres.Test.Post do filter(expr(title == "foo")) end - read :read do - primary?(true) - end - read(:allow_any) read :paginated do From 6cd11def0153431f1009d90ef92d46f9236a7c02 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 5 May 2024 06:00:22 -0400 Subject: [PATCH 0408/1215] chore: update ash_sql --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 7ef5dca0..838b476c 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0.0-rc and >= 3.0.0-rc.38")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.17")}, + {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.18")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 95bed17c..750c1e5f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.43", "2154c2cd38773c7ff56f00f1161505da02139e4b851b7a0a14ef929dc12bbfbf", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d9b8a2ae389426ffd373c4cede7e977a906a87bf290938e948e53e25d73886e3"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.17", "b55c4a71ba0e4cdfef905bcdfa9ebb74c75e59dd1c1b53fcfa1a8d81e9c58def", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0eda1e606959d6fb85c665f7465397792f0c128761db8177f1683807020e5f67"}, + "ash": {:hex, :ash, "3.0.0-rc.45", "6c71cb4045c84cfec37b5e6964b8c0391b2e78a037492a29f3d2094032253cac", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a3261257aee24a59c66db289163b9e16d91cdbc0c6b3ac7eaf64a8a8e45c842"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.18", "aa3cdd228f0b6a270afe726641470c8fa4ca4dfaff90d9b3d25750c423c2f667", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ab94ec4189a85ac7cb01060e9057603fef6aaf9f840e938e26e353d9e0458692"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From bc02a4d23c74d61eadd82e4c447d53744b1a4202 Mon Sep 17 00:00:00 2001 From: Frank Dugan III Date: Sun, 5 May 2024 05:08:21 -0500 Subject: [PATCH 0409/1215] feat: add timestamptz types (#266) --- lib/types/timestamptz.ex | 42 +++ lib/types/timestamptz_usec.ex | 34 ++ mix.exs | 4 +- .../test_repo/posts/20240504185511.json | 352 ++++++++++++++++++ .../20240504185511_migrate_resources22.exs | 23 ++ test/bulk_update_test.exs | 4 + test/support/resources/post.ex | 9 +- 7 files changed, 465 insertions(+), 3 deletions(-) create mode 100644 lib/types/timestamptz.ex create mode 100644 lib/types/timestamptz_usec.ex create mode 100644 priv/resource_snapshots/test_repo/posts/20240504185511.json create mode 100644 priv/test_repo/migrations/20240504185511_migrate_resources22.exs diff --git a/lib/types/timestamptz.ex b/lib/types/timestamptz.ex new file mode 100644 index 00000000..6388b2fb --- /dev/null +++ b/lib/types/timestamptz.ex @@ -0,0 +1,42 @@ +defmodule AshPostgres.Timestamptz do + @moduledoc """ + Implements the PostgresSQL [timestamptz](https://www.postgresql.org/docs/current/datatype-datetime.html) (aka `timestamp with time zone`) type. + + Postgres [*strongly recommends*](https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29) using this type instead of the standard timestamps/datetimes without a time zone. Generally speaking, it is best practice to use the [nanosecond-precision](`AshPostgres.TimestamptzUsec`) variant. + + The basic reason `timestamptz` exists is to guarantee that the precise moment in time is stored as microseconds since January 1st, 2000 in UTC. This guarantee eliminates many time arithmetic problems, and ensures portability. + + It does not actually store a timezone, in spite of the name. As far as Elixir/Ecto is concerned, it it always of type `DateTime` and set to UTC. Using this type ensures Postgres internally uses the same contract as Ecto's `:utc_datetime`, which is to always store `DateTime` in UTC. This is especially helpful if you need to do complex time arithmetic in SQL fragments, or build reports/materialized views that use localized time formatting. + + Using this type ubiquitously in your schemas is particularly beneficial for consistency, and this is currently [under consideration](https://github.com/ash-project/ash_postgres/issues/264) as a configuration option for the default datetime storage type. + + ```elixir + attribute :timestamp, AshPostgres.Timestamptz + timestamps type: AshPostgres.Timestamptz + ``` + + Alternatively, you can set up a shortname: + + ```elixir + # config.exs + config :ash, :custom_types, timestamptz: AshPostgres.Timestamptz + ``` + + After saving, you will need to run `mix compile ash --force`. + + ```elixir + attribute :timestamp, :timestamptz + timestamps type: :timestamptz + ``` + + + + + """ + use Ash.Type.NewType, subtype_of: :datetime, constraints: [precision: :second] + + @impl true + def storage_type(_constraints) do + :timestamptz + end +end diff --git a/lib/types/timestamptz_usec.ex b/lib/types/timestamptz_usec.ex new file mode 100644 index 00000000..513d455d --- /dev/null +++ b/lib/types/timestamptz_usec.ex @@ -0,0 +1,34 @@ +defmodule AshPostgres.TimestamptzUsec do + @moduledoc """ + Implements the PostgresSQL [timestamptz](https://www.postgresql.org/docs/current/datatype-datetime.html) (aka `timestamp with time zone`) type with nanosecond precision. + + ```elixir + attribute :timestamp, AshPostgres.TimestamptzUsec + timestamps type: AshPostgres.TimestamptzUsec + ``` + + Alternatively, you can set up a shortname: + + ```elixir + # config.exs + config :ash, :custom_types, timestamptz_usec: AshPostgres.TimestamptzUsec + ``` + + After saving, you will need to run `mix compile ash --force`. + + ```elixir + attribute :timestamp, :timestamptz_usec + timestamps type: :timestamptz_usec + ``` + + + + Please see `AshPostgres.Timestamptz` for details about the usecase for this type. + """ + use Ash.Type.NewType, subtype_of: :datetime, constraints: [precision: :microsecond] + + @impl true + def storage_type(_constraints) do + :"timestamptz(6)" + end +end diff --git a/mix.exs b/mix.exs index 838b476c..588047a9 100644 --- a/mix.exs +++ b/mix.exs @@ -135,7 +135,9 @@ defmodule AshPostgres.MixProject do Types: [ AshPostgres.Type, AshPostgres.Tsquery, - AshPostgres.Tsvector + AshPostgres.Tsvector, + AshPostgres.Timestamptz, + AshPostgres.TimestamptzUsec ], Extensions: [ AshPostgres.Extensions.Vector diff --git a/priv/resource_snapshots/test_repo/posts/20240504185511.json b/priv/resource_snapshots/test_repo/posts/20240504185511.json new file mode 100644 index 00000000..bfb05581 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240504185511.json @@ -0,0 +1,352 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "primary_key?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "timestamptz(6)", + "source": "datetime", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "timestamptz(6)", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "primary_key?": false, + "generated?": false + } + ], + "table": "posts", + "hash": "710D89B877C62B93CE292BA3084B75EF88097188F1B948BA531EEB5A2064BDC8", + "repo": "Elixir.AshPostgres.TestRepo", + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "where": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "unique": true, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "using": null, + "nulls_distinct": true, + "all_tenants?": false + } + ], + "base_filter": "type = 'sponsored'", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240504185511_migrate_resources22.exs b/priv/test_repo/migrations/20240504185511_migrate_resources22.exs new file mode 100644 index 00000000..86257d61 --- /dev/null +++ b/priv/test_repo/migrations/20240504185511_migrate_resources22.exs @@ -0,0 +1,23 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources22 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(:posts) do + modify(:updated_at, :"timestamptz(6)") + modify(:datetime, :"timestamptz(6)") + end + end + + def down do + alter table(:posts) do + modify(:datetime, :utc_datetime_usec) + modify(:updated_at, :utc_datetime_usec) + end + end +end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 9dc7389d..cc1b7ac2 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -38,6 +38,10 @@ defmodule AshPostgres.BulkUpdateTest do assert Enum.all?(posts, fn post -> DateTime.compare(post.datetime, now) == :eq end) + + assert Enum.all?(posts, fn post -> + DateTime.diff(now, post.updated_at, :minute) < 1 + end) end test "a map can be given as input" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 59b2e239..2e8c995e 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -170,7 +170,7 @@ defmodule AshPostgres.Test.Post do source(:title_column) end - attribute(:datetime, :utc_datetime_usec, public?: true) + attribute(:datetime, AshPostgres.TimestamptzUsec, public?: true) attribute(:score, :integer, public?: true) attribute(:public, :boolean, public?: true) attribute(:category, :ci_string, public?: true) @@ -200,7 +200,12 @@ defmodule AshPostgres.Test.Post do end create_timestamp(:created_at, writable?: true, public?: true) - update_timestamp(:updated_at, writable?: true, public?: true) + + update_timestamp(:updated_at, + type: AshPostgres.TimestamptzUsec, + writable?: true, + public?: true + ) end code_interface do From 4dd588c043138ec466cfa5028722afd4743ba18d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 5 May 2024 11:13:30 -0400 Subject: [PATCH 0410/1215] fix: honor dry_run option in extension migrations --- lib/migration_generator/migration_generator.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 392346c6..05bbff64 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -306,8 +306,14 @@ defmodule AshPostgres.MigrationGenerator do ) contents = format(migration_file, contents, opts) - create_file(snapshot_file, snapshot_contents, force: true) - create_file(migration_file, contents) + + if opts.dry_run do + Mix.shell().info(snapshot_contents) + Mix.shell().info(contents) + else + create_file(snapshot_file, snapshot_contents, force: true) + create_file(migration_file, contents) + end end end end From 3334999eb1c2a9e6286d1bfa67dc82d76dba127a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 5 May 2024 11:40:07 -0400 Subject: [PATCH 0411/1215] fix: properly pass old version in when migrating extensions --- lib/migration_generator/migration_generator.ex | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 05bbff64..2e6f1163 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -251,8 +251,18 @@ defmodule AshPostgres.MigrationGenerator do extensions_snapshot[:ash_functions_version] ) - {_ext_name, version, up_fn, _down_fn} when is_function(up_fn, 1) -> - up_fn.(version) + {ext_name, _version, up_fn, _down_fn} when is_function(up_fn, 1) -> + current_version = + Enum.find_value(extensions_snapshot[:installed] || [], 0, fn name -> + with ["", "v" <> version] <- String.split(name, to_string(ext_name)), + {integer, ""} <- Integer.parse(version) do + integer + else + _ -> nil + end + end) + + up_fn.(current_version) extension -> "execute(\"CREATE EXTENSION IF NOT EXISTS \\\"#{extension}\\\"\")" From fa7de435327704bd4d7076d71d746db9ff4b78e2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 5 May 2024 11:40:31 -0400 Subject: [PATCH 0412/1215] chore: release version v2.0.0-rc.15 --- CHANGELOG.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24351f4c..6de445f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,89 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.0-rc.15](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2024-05-05) +### Breaking Changes: + +* change defaults for uuids to `gen_random_uuid()` + +* Use UTC for default generated timestamps (#131) + +* 3.0 (#227) + + + +### Features: + +* add timestamptz types (#266) + +* test with calculations using `exists` (#240) + +* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Bug Fixes: + +* properly pass old version in when migrating extensions + +* honor dry_run option in extension migrations + +* update ash & fix subquery sort references + +* fix calculate when exprs aren't dynamics + +* ensure limit/offset triggers joining for update/destroy query + +* only reference `sub` if a subquery is created + +* update ash_sql for inner join fixes + +* fix argument order in AshSql.Bindings.default_bindings/4 (#251) + +* properly honor `limit` in bulk operations + +* undo change that expresses that atomics cant be done without `ash-functions` + +* handle missing aggregate relationships and fields better in transformers + +* update ash_sql for bug fixes + +* reproduce issue around atomic updates & validations + +* ensure that `exists` with a filter paired with `from_many?` functions properly + +* update ash_sql, fix credo + +* use proper sql implementation in `default_bindings` + +* don't wait for shell input when checking migrations + +* properly handle non-filter aggregate filters + +* ensure timestamps are present in extension migrations + +* handle fully fleshed out aggregate fields + +### Improvements: + +* support latest ash & calculate/3 capability + +* warn on missing ash-functions at compile time + +* support `mix ash.rollback` with interactive rollback + +* don't fetch version in agent when using sandbox + +* loosen 3.0 release candidate requirement + +* fixes for 3.0 changes and AshSql changes + +* move many internals out to `AshSql` package + +* add default implementation for pg_version, and rename to `min_pg_version` + +* upgrade to 3.0 + +* properly show unsupported error expression + ## [v2.0.0-rc.14](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-04-29) diff --git a/mix.exs b/mix.exs index 588047a9..6ec34343 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.0-rc.14" + @version "2.0.0-rc.15" def project do [ From 35fb3c7ea79b22013255c2ea4af6c64c922413d3 Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Mon, 6 May 2024 21:01:31 +0800 Subject: [PATCH 0413/1215] docs: Fix typo in page title (#267) --- documentation/topics/development/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/topics/development/testing.md b/documentation/topics/development/testing.md index 64052f97..3fe7662b 100644 --- a/documentation/topics/development/testing.md +++ b/documentation/topics/development/testing.md @@ -1,4 +1,4 @@ -# Testinging with AshPostgres +# Testing with AshPostgres When using AshPostgres resources in tests, you will likely want to include use a test case similar to the following. This will ensure that your repo runs everything in a transaction. From ca141cd0951bc7e3e259054efc196abe084cb9ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 09:01:58 -0400 Subject: [PATCH 0414/1215] chore(deps): bump ash_sql from 0.1.1-rc.18 to 0.1.1-rc.19 (#268) Bumps [ash_sql](https://github.com/ash-project/ash_sql) from 0.1.1-rc.18 to 0.1.1-rc.19. - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.1.1-rc.18...v0.1.1-rc.19) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 750c1e5f..38b7ad8d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0-rc.45", "6c71cb4045c84cfec37b5e6964b8c0391b2e78a037492a29f3d2094032253cac", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a3261257aee24a59c66db289163b9e16d91cdbc0c6b3ac7eaf64a8a8e45c842"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.18", "aa3cdd228f0b6a270afe726641470c8fa4ca4dfaff90d9b3d25750c423c2f667", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ab94ec4189a85ac7cb01060e9057603fef6aaf9f840e938e26e353d9e0458692"}, + "ash_sql": {:hex, :ash_sql, "0.1.1-rc.19", "1547c877de40c9b7c0e53ce33768e04e7f6dac4fb9217b453b9754592589a960", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "3c4ee1b8cf7755e637618a79443e92681a8350cb729027240651c244018dfb42"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 459d853d28ffb03085632670a16213abce6842f5 Mon Sep 17 00:00:00 2001 From: Jechol Lee Date: Mon, 6 May 2024 22:02:34 +0900 Subject: [PATCH 0415/1215] fix: resolve_renames that keeps columns to be renamed in list of columns to add (#269) --- lib/migration_generator/migration_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2e6f1163..33899e6f 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2400,7 +2400,7 @@ defmodule AshPostgres.MigrationGenerator do {rest_adding, rest_removing, rest_renames} = resolve_renames(table, new_adding, rest, opts) - {new_adding ++ rest_adding, new_removing ++ rest_removing, rest_renames ++ new_renames} + {rest_adding, new_removing ++ rest_removing, rest_renames ++ new_renames} end defp renaming_to?(table, removing, adding, opts) do From f6b1aa3d29927c0e84e8b73cc896f7dcc4938a4e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 6 May 2024 11:26:41 -0400 Subject: [PATCH 0416/1215] fix: ensure we don't duplicate selects on destroy_query calls we fixed this by combining the logic for query building for bulk destroys and bulk updates --- lib/data_layer.ex | 161 +++++++++++-------------------------- test/bulk_destroy_test.exs | 5 +- 2 files changed, 49 insertions(+), 117 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 430a1f5f..f36ded27 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1347,12 +1347,7 @@ defmodule AshPostgres.DataLayer do Enum.any?(query.joins, &(&1.qual != :inner)) || query.limit || query.offset if needs_to_join? do - root_query = - from(row in query.from.source, as: ^0) - |> Map.put(:__ash_bindings__, query.__ash_bindings__) - |> Ecto.Query.exclude(:select) - |> Map.put(:limit, query.limit) - |> Map.put(:offset, query.offset) + root_query = Ecto.Query.exclude(query, :select) root_query = if query.limit || query.offset do @@ -1381,24 +1376,7 @@ defmodule AshPostgres.DataLayer do ) |> Map.put(:__ash_bindings__, query.__ash_bindings__) - joins_to_add = - for {%{on: on} = join, ix} <- Enum.with_index(query.joins) do - %{join | on: Ecto.Query.Planner.rewrite_sources(on, &(&1 + 1)), ix: ix + 1} - end - - {:ok, - %{ - faked_query - | joins: faked_query.joins ++ joins_to_add, - aliases: Map.new(query.aliases, fn {key, val} -> {key, val + 1} end), - limit: nil, - offset: nil, - wheres: - faked_query.wheres ++ - Enum.map(query.wheres, fn where -> - Ecto.Query.Planner.rewrite_sources(where, &(&1 + 1)) - end) - }} + {:ok, faked_query} else {:ok, query @@ -1423,108 +1401,61 @@ defmodule AshPostgres.DataLayer do data end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :update, true) - - try do - query = - query - |> AshSql.Bindings.default_bindings( - resource, - AshPostgres.SqlImplementation, - changeset.context - ) - - query = - if options[:return_records?] do - attrs = resource |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name) - - Ecto.Query.select(query, ^attrs) - else - query - end - |> Ecto.Query.exclude(:order_by) - - repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + |> ecto_changeset(changeset, :delete, true) - repo_opts = - AshSql.repo_opts( - repo, - AshPostgres.SqlImplementation, - changeset.timeout, - changeset.tenant, - changeset.resource - ) + case bulk_updatable_query( + query, + resource, + changeset.atomics, + options[:calculations] || [], + changeset.context + ) do + {:error, error} -> + {:error, error} - needs_to_join? = - Enum.any?(query.joins, &(&1.qual != :inner)) || query.limit || query.offset + {:ok, query} -> + try do + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - query = - if needs_to_join? do - {:ok, root_query} = - from(row in query.from.source, []) - |> AshSql.Bindings.default_bindings( - resource, + repo_opts = + AshSql.repo_opts( + repo, AshPostgres.SqlImplementation, - changeset.context + changeset.timeout, + changeset.tenant, + changeset.resource ) - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - |> add_calculations(options[:calculations] || [], resource) - on = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row, distinct], - ^dynamic and field(row, ^key) == field(distinct, ^key) - ) - else - Ecto.Query.dynamic([row, distinct], field(row, ^key) == field(distinct, ^key)) - end - end) + query = + if options[:return_records?] do + {:ok, query} = + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + |> add_calculations(options[:calculations] || [], resource) - from(row in root_query, - select: row, - join: subquery(query), - as: :sub, - on: ^on - ) - else - Ecto.Query.exclude(query, :order_by) - end + query + else + Ecto.Query.exclude(query, :select) + end - query = - if options[:return_records?] do - if Enum.any?(query.joins, &(&1.qual != :inner)) do - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([sub: sub], sub) + {_, results} = + with_savepoint(repo, query, fn -> + repo.delete_all( + query, + repo_opts + ) + end) + + if options[:return_records?] do + {:ok, remap_mapped_fields(results, query)} else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([row], row) + :ok end - else - query - |> Ecto.Query.exclude(:select) + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) end - - {_, results} = - with_savepoint(repo, query, fn -> - repo.delete_all( - query, - repo_opts - ) - end) - - if options[:return_records?] do - {:ok, remap_mapped_fields(results, query)} - else - :ok - end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) end end diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index fa55a6a4..09d7e9da 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -22,10 +22,10 @@ defmodule AshPostgres.BulkDestroyTest do Post |> Ash.Query.filter(title == "fred") - |> Ash.bulk_destroy!(:update, %{}) + |> Ash.bulk_destroy!(:destroy, %{}) # 😢 sad - assert [%{title: "george"}] = Ash.read!(Post) + assert ["george"] = Ash.read!(Post) |> Enum.map(& &1.title) end test "the query can join to related tables when necessary" do @@ -33,6 +33,7 @@ defmodule AshPostgres.BulkDestroyTest do Post |> Ash.Query.filter(author.first_name == "fred" or title == "fred") + |> Ash.Query.select([:title]) |> Ash.bulk_destroy!(:update, %{}, return_records?: true) assert [%{title: "george"}] = Ash.read!(Post) From 1cd808244b985541f0dc05c83e7e65a20faf2307 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 12:02:06 -0400 Subject: [PATCH 0417/1215] chore(deps): bump ash from 3.0.0-rc.45 to 3.0.0-rc.46 (#272) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 38b7ad8d..7cb39b17 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.45", "6c71cb4045c84cfec37b5e6964b8c0391b2e78a037492a29f3d2094032253cac", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a3261257aee24a59c66db289163b9e16d91cdbc0c6b3ac7eaf64a8a8e45c842"}, + "ash": {:hex, :ash, "3.0.0-rc.46", "8c84ca24003c3e678f84f51da79a6b3edd1d808ccace53e207776e7d15a8c5f7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c833ba90e76a17cf5b9386bc47626d943f5da0908a5e7b850433f9db3e79784c"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.19", "1547c877de40c9b7c0e53ce33768e04e7f6dac4fb9217b453b9754592589a960", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "3c4ee1b8cf7755e637618a79443e92681a8350cb729027240651c244018dfb42"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 4e64c6e70a0d4789ebba95279893f5e29f73684e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 8 May 2024 18:40:08 -0400 Subject: [PATCH 0418/1215] docs: revamp changelog --- CHANGELOG.md | 4029 +------------------------------- documentation/1.0-CHANGELOG.md | 3903 +++++++++++++++++++++++++++++++ 2 files changed, 3921 insertions(+), 4011 deletions(-) create mode 100644 documentation/1.0-CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6de445f9..2a03bbf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,4027 +5,34 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline -## [v2.0.0-rc.15](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2024-05-05) -### Breaking Changes: - -* change defaults for uuids to `gen_random_uuid()` - -* Use UTC for default generated timestamps (#131) - -* 3.0 (#227) - - - -### Features: - -* add timestamptz types (#266) - -* test with calculations using `exists` (#240) - -* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) - -### Bug Fixes: - -* properly pass old version in when migrating extensions - -* honor dry_run option in extension migrations - -* update ash & fix subquery sort references - -* fix calculate when exprs aren't dynamics - -* ensure limit/offset triggers joining for update/destroy query - -* only reference `sub` if a subquery is created - -* update ash_sql for inner join fixes - -* fix argument order in AshSql.Bindings.default_bindings/4 (#251) - -* properly honor `limit` in bulk operations - -* undo change that expresses that atomics cant be done without `ash-functions` - -* handle missing aggregate relationships and fields better in transformers - -* update ash_sql for bug fixes - -* reproduce issue around atomic updates & validations - -* ensure that `exists` with a filter paired with `from_many?` functions properly - -* update ash_sql, fix credo - -* use proper sql implementation in `default_bindings` - -* don't wait for shell input when checking migrations - -* properly handle non-filter aggregate filters - -* ensure timestamps are present in extension migrations - -* handle fully fleshed out aggregate fields - -### Improvements: - -* support latest ash & calculate/3 capability - -* warn on missing ash-functions at compile time - -* support `mix ash.rollback` with interactive rollback - -* don't fetch version in agent when using sandbox - -* loosen 3.0 release candidate requirement - -* fixes for 3.0 changes and AshSql changes - -* move many internals out to `AshSql` package - -* add default implementation for pg_version, and rename to `min_pg_version` - -* upgrade to 3.0 - -* properly show unsupported error expression - -## [v2.0.0-rc.14](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-04-29) - - - - -### Improvements: - -* support latest ash & calculate/3 capability - -## [v2.0.0-rc.13](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.12...v2.0.0-rc.13) (2024-04-27) - - - - -### Bug Fixes: - -* ensure limit/offset triggers joining for update/destroy query - -* only reference `sub` if a subquery is created - -## [v2.0.0-rc.12](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2024-04-27) - - - - -### Bug Fixes: - -* update ash_sql for inner join fixes - -* fix argument order in AshSql.Bindings.default_bindings/4 (#251) - -## [v2.0.0-rc.11](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2024-04-24) - - - - -### Bug Fixes: - -* properly honor `limit` in bulk operations - -## [v2.0.0-rc.10](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2024-04-23) - - - - -### Bug Fixes: - -* undo change that expresses that atomics cant be done without `ash-functions` - -## [v2.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.8...v2.0.0-rc.9) (2024-04-23) -### Breaking Changes: - -* change defaults for uuids to `gen_random_uuid()` - -* Use UTC for default generated timestamps (#131) - -* 3.0 (#227) - - - -### Features: - -* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) - -### Bug Fixes: - -* handle missing aggregate relationships and fields better in transformers - -* update ash_sql for bug fixes - -* reproduce issue around atomic updates & validations - -* ensure that `exists` with a filter paired with `from_many?` functions properly - -* update ash_sql, fix credo - -* use proper sql implementation in `default_bindings` - -* don't wait for shell input when checking migrations - -* properly handle non-filter aggregate filters - -* ensure timestamps are present in extension migrations - -* handle fully fleshed out aggregate fields - -### Improvements: - -* warn on missing ash-functions at compile time - -* support `mix ash.rollback` with interactive rollback - -* don't fetch version in agent when using sandbox - -* loosen 3.0 release candidate requirement - -* fixes for 3.0 changes and AshSql changes - -* move many internals out to `AshSql` package - -* add default implementation for pg_version, and rename to `min_pg_version` - -* upgrade to 3.0 - -* properly show unsupported error expression - -## [v2.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2024-04-22) - - - - -### Bug Fixes: - -* ensure that `exists` with a filter paired with `from_many?` functions properly - -## [v2.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2024-04-12) - - - - -### Bug Fixes: +## [v2.0.0](https://github.com/ash-project/ash_postgres/compare/v2.0.0...2.0) -* update ash_sql, fix credo - -## [v2.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.5...v2.0.0-rc.6) (2024-04-10) - - - - -### Bug Fixes: - -* use proper sql implementation in `default_bindings` - -### Improvements: - -* support `mix ash.rollback` with interactive rollback - -## [v2.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.4...v2.0.0-rc.5) (2024-04-05) - - - - -### Bug Fixes: - -* don't wait for shell input when checking migrations - -### Improvements: - -* don't fetch version in agent when using sandbox - -## [v2.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.3...v2.0.0-rc.4) (2024-04-02) - - - - -### Improvements: - -* loosen 3.0 release candidate requirement - -## [v2.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2024-04-01) - - - - -### Improvements: - -* fixes for 3.0 changes and AshSql changes - -* move many internals out to `AshSql` package - -## [v2.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.1...v2.0.0-rc.2) (2024-03-29) -### Breaking Changes: - -* change defaults for uuids to `gen_random_uuid()` - -* Use UTC for default generated timestamps (#131) - -* 3.0 (#227) - - - -### Features: - -* add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) - -### Bug Fixes: - -* properly handle non-filter aggregate filters - -* ensure timestamps are present in extension migrations - -* handle fully fleshed out aggregate fields - -### Improvements: - -* add default implementation for pg_version, and rename to `min_pg_version` - -* upgrade to 3.0 - -* properly show unsupported error expression - -## [v2.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.0...v2.0.0-rc.1) (2024-03-28) - - - - -### Improvements: - -* add default implementation for pg_version, and rename to `min_pg_version` - -## [v2.0.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.5.22...v2.0.0-rc.0) (2024-03-27) +The changelog is starting over. Please see `/documentation/1.0-CHANGELOG.md` in GitHub for previous changelogs. ### Breaking Changes: -- change defaults for uuids to `gen_random_uuid()` - -- Use UTC for default generated timestamps (#131) +- [Ash.Type.UUID] change defaults in migrations for uuids to `gen_random_uuid()` +- [Ash.Type.DateTime] Use UTC for default generated timestamps (#131) +- [AshPostgres.DataLayer] must now know the min_pg_version that will be used. By default we check this at repo startup by asking the database, but you can also define it yourself. +- [AshPostgres.DataLayer] Now requires postgres version 14 or higher ### Features: -- add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) - -### Improvements: - -- show proper error when using error expresison without `ash-functions` extension - -## [v1.5.22](https://github.com/ash-project/ash_postgres/compare/v1.5.21...v1.5.22) (2024-03-20) - -### Bug Fixes: - -- don't fail on aggregate query generation - -## [v1.5.21](https://github.com/ash-project/ash_postgres/compare/v1.5.20...v1.5.21) (2024-03-20) - -### Bug Fixes: - -- properly format migrations - -- ensure exists aggregates have filters included - -## [v1.5.20](https://github.com/ash-project/ash_postgres/compare/v1.5.19...v1.5.20) (2024-03-20) - -### Bug Fixes: - -- undo default of nulls_distinct option to true (#223) - -- generate correct custom index name in down migration function (#222) - -## [v1.5.19](https://github.com/ash-project/ash_postgres/compare/v1.5.18...v1.5.19) (2024-03-19) - -### Bug Fixes: - -- encode maps on update using fragments - -### Improvements: - -- Add nulls_distinct option to CustomIndex (#221) - -## [v1.5.18](https://github.com/ash-project/ash_postgres/compare/v1.5.17...v1.5.18) (2024-03-19) - -### Bug Fixes: - -- don't reuse binding in many to many aggregate joins - -- typo in extension generator creates invalid drop - -- merge base_filter and custom index's where correctly (#219) - -### Improvements: - -- properly format generated migrations - -- don't select fields in exists subquery - -## [v1.5.17](https://github.com/ash-project/ash_postgres/compare/v1.5.16...v1.5.17) (2024-03-06) - -### Bug Fixes: - -- prevent ecto/pg from getting confused about the type of maps - -## [v1.5.16](https://github.com/ash-project/ash_postgres/compare/v1.5.15...v1.5.16) (2024-03-05) +- [AshPostgres.Timestamptz] add timestamptz types (#266) +- [AshPostgres.Repo] add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) +- [AshPostgres.DataLayer] support `c:AshDataLayer.calculate/3` capability ### Bug Fixes: -- always exclude `:order_by` on bulk updateable query - -- don't apply join relationship sort for lateral join - -## [v1.5.15](https://github.com/ash-project/ash_postgres/compare/v1.5.14...v1.5.15) (2024-03-01) - -### Improvements: - -- don't double cast to the same type - -- detect more types - -## [v1.5.14](https://github.com/ash-project/ash_postgres/compare/v1.5.13...v1.5.14) (2024-03-01) +- [AshPostgres.MigrationGenerator] honor dry_run option in extension migrations +- [AshPostgres.MigrationGenerator] don't wait for shell input when checking migrations +- [AshPostgres.DataLayer] ensure limit/offset triggers joining for update/destroy query +- [AshPostgres.DataLayer] properly honor `limit` in bulk operations +- [AshPostgres.DataLayer] ensure that `exists` with a filter paired with `from_many?` functions properly ### Improvements: -- no need for subquery for simple table aliases - -## [v1.5.13](https://github.com/ash-project/ash_postgres/compare/v1.5.12...v1.5.13) (2024-02-29) - -### Bug Fixes: - -- properly handle multiple sorts in aggregate - -## [v1.5.12](https://github.com/ash-project/ash_postgres/compare/v1.5.11...v1.5.12) (2024-02-29) - -### Bug Fixes: - -- ensure that `from_many?` joins are properly limited - -- ensure that lateral joins are properly filtered - -## [v1.5.11](https://github.com/ash-project/ash_postgres/compare/v1.5.10...v1.5.11) (2024-02-29) - -### Bug Fixes: - -- simplify(and fix) exists subquery generation - -- properly leverage subqueries throughout relationship joining - -- migration generator extensions in multiple repos (#214) - -- Migration generator for extensions in multiple repos - -### Improvements: - -- optimize more cases for simple join aggregates - -## [v1.5.10](https://github.com/ash-project/ash_postgres/compare/v1.5.9...v1.5.10) (2024-02-26) - -### Bug Fixes: - -- fix error when encoding vectors - -- ensure select is applied (or not) properly in bulk update/destroys - -## [v1.5.9](https://github.com/ash-project/ash_postgres/compare/v1.5.8...v1.5.9) (2024-02-25) - -### Bug Fixes: - -- handle more subquery filter cases for aggregates - -- only apply filters inside aggregate subquery - -### Improvements: - -- add test for aggregates - -## [v1.5.8](https://github.com/ash-project/ash_postgres/compare/v1.5.7...v1.5.8) (2024-02-24) - -### Bug Fixes: - -- properly handle complex types in lists - -## [v1.5.7](https://github.com/ash-project/ash_postgres/compare/v1.5.6...v1.5.7) (2024-02-22) - -### Bug Fixes: - -- properly apply lateral join conditions to left lateral joins - -## [v1.5.6](https://github.com/ash-project/ash_postgres/compare/v1.5.5...v1.5.6) (2024-02-21) - -### Bug Fixes: - -- ensure select is properly set on delete_all - -### Improvements: - -- optimize aggregate query filtering - -## [v1.5.5](https://github.com/ash-project/ash_postgres/compare/v1.5.4...v1.5.5) (2024-02-21) - -### Bug Fixes: - -- ensure proper return value for single aggregate runs - -## [v1.5.4](https://github.com/ash-project/ash_postgres/compare/v1.5.3...v1.5.4) (2024-02-21) - -### Bug Fixes: - -- don't sort a query that will be used with `delete_all` - -- ensure that `exists?` aggregates use `repo.exists?` - -- properly handle to_many joins in aggregates - -- honor aggregate query filters - -- use proper tables in joins originating from polymorphic resource (#211) - -- properly transfer table names to non-inner wrapper queries (#210) - -## [v1.5.3](https://github.com/ash-project/ash_postgres/compare/v1.5.2...v1.5.3) (2024-02-19) - -### Bug Fixes: - -- handle non-inner joins in delete_all - -- handle non-inner joins in update - -## [v1.5.2](https://github.com/ash-project/ash_postgres/compare/v1.5.1...v1.5.2) (2024-02-19) - -### Bug Fixes: - -- don't update_all or delete_all with `order_by` - -- handle updating from queries w/ non-inner initial joins - -## [v1.5.1](https://github.com/ash-project/ash_postgres/compare/v1.5.0...v1.5.1) (2024-02-19) - -### Bug Fixes: - -- joining to `from_many?: true` relationships not honoring limit - -## [v1.5.0](https://github.com/ash-project/ash_postgres/compare/v1.4.0...v1.5.0) (2024-02-16) - -### Features: - -- Make MigrationGenerator accept atoms (#201) - -### Bug Fixes: - -- allow subquerying a `through` while aggregating a many to many - -- don't subquery if we need to reference `parent_as` - -- avoid double wrapping in subqueries - -- properly set 0 binding on joined subquery creation - -- properly alter renaming attributes in migration generator - -- handle original data not available in destroy_query - -- use primary key of source as join key - -- use pkey if error fields is empty - -- forgot to bind keys to a variable 🤦🏻 - -- ensure identity keys is never missing - -- properly build subqueries when required for relationship queries - -- only migrate/rollback one repo at a time - -- proper return types for updates from queries - -- allow atomics to return `nil` - -- Correct the matching used in building a distinct expression (#196) - -- only rollback to savepoint on specific errors - -- keep fields of `custom_index` in format that they were provided (#195) - -- remap selected fields, don't subquery in aggregate joins - -- include explicit schema in snapshot folder name - -- Support all_tenants? in custom index (#194) - -### Improvements: - -- update to latest ash - -- mark (i)like functions as predicates (#205) - -- detect bigserial when altering attributes - -- Include modules in installed_extensions return type (#202) - -- don't drop primary key in case of removal - -- handle if select is present on query - -- support `Ash.Changeset.OriginalDataNotAvailable` - -- support `count_nils` expression - -- `error_fields` for `custom_index` - -- support latest ash changes - -## [v1.4.0](https://github.com/ash-project/ash_postgres/compare/v1.3.68...v1.4.0) (2024-01-12) - -### Features: - -- Add unit test to check lateral joins - -### Bug Fixes: - -- unset sort/distinct on related queries - -- subquery relationships that have filters - -- don't overwrite manually set schema on lateral join query - -- properly configure `polymorphic_name` option - -- honor configured schema on bulk create - -### Improvements: - -- support `all_tenants?` option for identities - -- support `all_tenants?` option for custom indexes - -- support join_filters on aggregates - -- use the target action when generating related queries - -## [v1.3.68](https://github.com/ash-project/ash_postgres/compare/v1.3.67...v1.3.68) (2024-01-04) - -### Bug Fixes: - -- properly gather types for operator & function overloads - -## [v1.3.67](https://github.com/ash-project/ash_postgres/compare/v1.3.66...v1.3.67) (2024-01-04) - -### Bug Fixes: - -- support encoding errors with expressions in them - -### Improvements: - -- support latest ash version & operator overrides - -- support new bulk operations - -## [v1.3.66](https://github.com/ash-project/ash_postgres/compare/v1.3.65...v1.3.66) (2023-12-30) - -### Improvements: - -- support new `return_query/2` callback - -- support new `:no_rollback` error signal - -- require `name` when generating migrations - -- support directly referencing aggregates from aggregates - -- support aggregates as `get_path` subject - -## [v1.3.65](https://github.com/ash-project/ash_postgres/compare/v1.3.64...v1.3.65) (2023-12-23) - -### Bug Fixes: - -- various fixes for unnecessary aggregate additions - -- use lateral joins when joining to subquery w/ parent reference - -- replace upsert field with source in EXCLUDED fragment (#187) - -- handle strings in get_path - -- reenable mix tasks that need calling - -### Improvements: - -- support aggregates using other aggregates - -- support string_length and string_trim - -- only start savepoints when necessary - -- clean up nested if statements to single case statements - -- support for `error/2` expression - -## [v1.3.64](https://github.com/ash-project/ash_postgres/compare/v1.3.63...v1.3.64) (2023-12-04) - -### Bug Fixes: - -- properly cast lazy update defaults to target type - -## [v1.3.63](https://github.com/ash-project/ash_postgres/compare/v1.3.62...v1.3.63) (2023-12-03) - -### Bug Fixes: - -- use maps for composite_type instead of tuples - -- avoid empty error on upserts with `:nothing` - -- simplify aggregate bindings & calculation reference building - -- hydrate aggregate refs when adding for calculations - -- apply limit to `from_many?` relationship joins - -- properly add filters for exists aggregates - -- properly expand calculation values across aggregate invocations - -- don't add filter for `no_attributes?` relationships - -- handle `no_attributes?` flag on aggregates better - -- properly handle sorted relationships in aggregates - -### Improvements: - -- support `composite_type/2` expression - -- support composite types - -- optimize relationships with identity on other end - -- allow specifying multi-column foreign keys (#180) - -- add match_with option on references - -- add match_type option on references - -## [v1.3.62](https://github.com/ash-project/ash_postgres/compare/v1.3.61...v1.3.62) (2023-11-16) - -### Bug Fixes: - -- use `synonymous_relationship_path` when looking up ref bindings - -- add calculation context to calculation expressions - -## [v1.3.61](https://github.com/ash-project/ash_postgres/compare/v1.3.60...v1.3.61) (2023-11-15) - -### Bug Fixes: - -- don't append update_defaults automatically if `upsert_fields` was set - -- don't ensure repo compiled at compile time - -- handle additional case for new functional repo callback - -- get resource from proper bindings on `exists` query - -### Improvements: - -- support a 2 argument function for the repo option - -- spport `CURRENT_DATE` default - -## [v1.3.60](https://github.com/ash-project/ash_postgres/compare/v1.3.59...v1.3.60) (2023-10-27) - -### Improvements: - -- support `parent` in sort expressions - -## [v1.3.59](https://github.com/ash-project/ash_postgres/compare/v1.3.58...v1.3.59) (2023-10-25) - -### Improvements: - -- join relationships for aggregate filters - -## [v1.3.58](https://github.com/ash-project/ash_postgres/compare/v1.3.57...v1.3.58) (2023-10-24) - -### Bug Fixes: - -- don't traverse new types for storage type - -- properly join to related references in relationship filters - -## [v1.3.57](https://github.com/ash-project/ash_postgres/compare/v1.3.56...v1.3.57) (2023-10-17) - -### Improvements: - -- allow for combining `AshPostgres.Repo` with other repos - -## [v1.3.56](https://github.com/ash-project/ash_postgres/compare/v1.3.55...v1.3.56) (2023-10-11) - -### Bug Fixes: - -- don't raise all errors - -## [v1.3.55](https://github.com/ash-project/ash_postgres/compare/v1.3.54...v1.3.55) (2023-10-11) - -### Improvements: - -- support atomics on upserts - -## [v1.3.54](https://github.com/ash-project/ash_postgres/compare/v1.3.53...v1.3.54) (2023-10-10) - -### Bug Fixes: - -- fix type specification for foreign_key_names - -## [v1.3.53](https://github.com/ash-project/ash_postgres/compare/v1.3.52...v1.3.53) (2023-10-10) - -### Bug Fixes: - -- don't run main query if only `exists` aggs are specified - -- subquery aggregate if limit is applied - -### Improvements: - -- update ash dependency - -- support `:ci_string` as a storage_type - -- support to-one references in calculations - -## [v1.3.52](https://github.com/ash-project/ash_postgres/compare/v1.3.51...v1.3.52) (2023-09-26) - -### Bug Fixes: - -- use `:wrap_list` type instead of custom validaitons (#167) - -### Improvements: - -- fix `upsert_fields` behavior for upserts - -- support data_layer_context option on transactions - -## [v1.3.51](https://github.com/ash-project/ash_postgres/compare/v1.3.50...v1.3.51) (2023-09-20) - -### Improvements: - -- add `AshPostgres.Tsvector` - -- add AshPostgres.Tsquery - -- support vector types and `vector_cosine_distance` - -## [v1.3.50](https://github.com/ash-project/ash_postgres/compare/v1.3.49...v1.3.50) (2023-09-06) - -### Improvements: - -- Allow resources to opt out of the primary key requirement. (#166) - -## [v1.3.49](https://github.com/ash-project/ash_postgres/compare/v1.3.48...v1.3.49) (2023-09-04) - -### Improvements: - -- implement ash lifecycle tasks - -## [v1.3.48](https://github.com/ash-project/ash_postgres/compare/v1.3.47...v1.3.48) (2023-09-04) - -### Improvements: - -- better error message for missing table config - -## [v1.3.47](https://github.com/ash-project/ash_postgres/compare/v1.3.46...v1.3.47) (2023-08-31) - -### Bug Fixes: - -- ensure we always select at least one field, and change one field - -## [v1.3.46](https://github.com/ash-project/ash_postgres/compare/v1.3.45...v1.3.46) (2023-08-31) - -### Bug Fixes: - -- use provided values for updates - -## [v1.3.45](https://github.com/ash-project/ash_postgres/compare/v1.3.44...v1.3.45) (2023-08-31) - -### Bug Fixes: - -- don't clobber loaded data on update - -## [v1.3.44](https://github.com/ash-project/ash_postgres/compare/v1.3.43...v1.3.44) (2023-08-31) - -### Bug Fixes: - -- properly handle ensure nsted calls to `get_path` are jsonb - -### Improvements: - -- support atomics (#165) - -## [v1.3.43](https://github.com/ash-project/ash_postgres/compare/v1.3.42...v1.3.43) (2023-08-22) - -### Bug Fixes: - -- properly provide constraints on all type casting - -## [v1.3.42](https://github.com/ash-project/ash_postgres/compare/v1.3.41...v1.3.42) (2023-08-22) - -### Bug Fixes: - -- support non-atom named aggregates - -- handle case where multiple grouped aggregates depend on further aggregates - -### Improvements: - -- support in-line aggregates - -- specify @behaviour in AshPostgres.Type - -- add `value_to_postgres_default/3` and `AshPostgres.Type` - -- handle non-cast-in-type queries - -## [v1.3.41](https://github.com/ash-project/ash_postgres/compare/v1.3.40...v1.3.41) (2023-08-08) - -### Bug Fixes: - -- handle interaction between distinct, join filters and sort - -### Improvements: - -- custom-extension implementation (#162) - -- custom-extension implementation - -- allow adding custom-extension by module's reference and fixes formatting - -- support new `from_many?` option - -- subquery after distinct to handle distinct - -## [v1.3.40](https://github.com/ash-project/ash_postgres/compare/v1.3.39...v1.3.40) (2023-08-01) - -### Bug Fixes: - -- properly detect optimizable first aggregates - -## [v1.3.39](https://github.com/ash-project/ash_postgres/compare/v1.3.38...v1.3.39) (2023-08-01) - -### Bug Fixes: - -- properly alter deferrability on attribute alter - -### Improvements: - -- update ash - -- handle empty maps in migration defaults automatically - -- handle empty lists in migraiton defaults automatically - -- apply sort in subqueries properly - -- handle `no_attributes?` better in more places - -- support the new `parent/1` expr in relationships - -- explicitly lock the source row - -## [v1.3.38](https://github.com/ash-project/ash_postgres/compare/v1.3.37...v1.3.38) (2023-07-21) - -### Bug Fixes: - -- un-break aggregates referencing calculations - -### Improvements: - -- properly handle context for referenced calculations - -## [v1.3.37](https://github.com/ash-project/ash_postgres/compare/v1.3.36...v1.3.37) (2023-07-19) - -### Improvements: - -- support new `distinct_sort` option - -## [v1.3.36](https://github.com/ash-project/ash_postgres/compare/v1.3.35...v1.3.36) (2023-07-19) - -### Bug Fixes: - -- type casting improvements, handle manual relationships in `exists` - -- protected names in conflict_target (#158) - -## [v1.3.35](https://github.com/ash-project/ash_postgres/compare/v1.3.34...v1.3.35) (2023-07-18) - -### Improvements: - -- support new `distinct` features from ash core - -## [v1.3.34](https://github.com/ash-project/ash_postgres/compare/v1.3.33...v1.3.34) (2023-07-18) - -### Improvements: - -- support unary `-/1` operator - -## [v1.3.33](https://github.com/ash-project/ash_postgres/compare/v1.3.32...v1.3.33) (2023-07-14) - -### Bug Fixes: - -- convert `Ash.Resource.Aggregate` to `Ash.Query.Aggregate` when adding - -### Improvements: - -- support `deferrable` option in migration generator - -- support `exists` aggregates - -## [v1.3.32](https://github.com/ash-project/ash_postgres/compare/v1.3.31...v1.3.32) (2023-07-12) - -### Improvements: - -- support `at/2` expression - -## [v1.3.31](https://github.com/ash-project/ash_postgres/compare/v1.3.30...v1.3.31) (2023-07-12) - -### Bug Fixes: - -- raise better error on invalid filter values - -- Fixes multiple schema identities migrations (#156) - -- fix Logger deprecations for elixir 1.15 (#155) - -- interpolate table names with `inspect` in generated migrations (#152) - -### Improvements: - -- better `ash_functions` message - -- support `string_split` - -- add postgres expressions guide - -- add `simple_join_first_aggregates` option - -## [v1.3.30](https://github.com/ash-project/ash_postgres/compare/v1.3.29...v1.3.30) (2023-06-06) - -### Bug Fixes: - -- handle changing custom index names better - -- validate custom index names - -## [v1.3.29](https://github.com/ash-project/ash_postgres/compare/v1.3.28...v1.3.29) (2023-06-05) - -### Bug Fixes: - -- properly handle nested aggregate references - -## [v1.3.28](https://github.com/ash-project/ash_postgres/compare/v1.3.27...v1.3.28) (2023-05-23) - -### Bug Fixes: - -- handle raised errors in bulk actions - -## [v1.3.27](https://github.com/ash-project/ash_postgres/compare/v1.3.26...v1.3.27) (2023-05-17) - -### Improvements: - -- raise better errors on conflicting locks - -## [v1.3.26](https://github.com/ash-project/ash_postgres/compare/v1.3.25...v1.3.26) (2023-05-16) - -### Bug Fixes: - -- use proper lock list again - -- use proper list of row level locks - -- check `changeset.action_type` not `changeset.action.type` - -### Improvements: - -- support more lock types - -## [v1.3.25](https://github.com/ash-project/ash_postgres/compare/v1.3.24...v1.3.25) (2023-05-08) - -### Improvements: - -- support changeset.filters (for optimistic locking) - -## [v1.3.24](https://github.com/ash-project/ash_postgres/compare/v1.3.23...v1.3.24) (2023-05-03) - -### Improvements: - -- support bulk upserts - -## [v1.3.23](https://github.com/ash-project/ash_postgres/compare/v1.3.22...v1.3.23) (2023-05-01) - -### Bug Fixes: - -- don't incorrectly mark references as primary key references - -- go back to old migration sorting algorithm - -## [v1.3.22](https://github.com/ash-project/ash_postgres/compare/v1.3.21...v1.3.22) (2023-04-28) - -### Improvements: - -- support locking - -## [v1.3.21](https://github.com/ash-project/ash_postgres/compare/v1.3.20...v1.3.21) (2023-04-27) - -### Improvements: - -- handle new spark versions better, more explicit snapshots - -## [v1.3.20](https://github.com/ash-project/ash_postgres/compare/v1.3.19...v1.3.20) (2023-04-22) - -### Bug Fixes: - -- subquery aggregates when a distinct is being added - -- don't call `.table` on `nil` - -- wrap `datetime_add` in parenthesis - -- handle primary key changes properly - -### Improvements: - -- update ash - -- don't call `.table` on `nil` `snapshot` - -- use digraph for operation ordering - -## [v1.3.19](https://github.com/ash-project/ash_postgres/compare/v1.3.18...v1.3.19) (2023-04-07) - -### Bug Fixes: - -- properly handle newtypes, add test - -- honor newtypes when determining migration type - -- handle nil ash_functions_version in another place - -- handle nil ash_functions_version - -### Improvements: - -- update ash - -## [v1.3.18](https://github.com/ash-project/ash_postgres/compare/v1.3.17...v1.3.18) (2023-03-23) - -## [v1.3.17](https://github.com/ash-project/ash_postgres/compare/v1.3.16...v1.3.17) (2023-03-20) - -### Bug Fixes: - -- properly map `parent` bindings in `exists` - -## [v1.3.16](https://github.com/ash-project/ash_postgres/compare/v1.3.15...v1.3.16) (2023-03-03) - -### Improvements: - -- support new date expressions - -## [v1.3.15](https://github.com/ash-project/ash_postgres/compare/v1.3.14...v1.3.15) (2023-02-23) - -### Improvements: - -- add aggregates used by sorts - -## [v1.3.14](https://github.com/ash-project/ash_postgres/compare/v1.3.13...v1.3.14) (2023-02-21) - -### Improvements: - -- Implement string_join expr (#132) - -## [v1.3.13](https://github.com/ash-project/ash_postgres/compare/v1.3.12...v1.3.13) (2023-02-17) - -### Bug Fixes: - -- don't use `:distinct` when `uniq?` is not `true` - -## [v1.3.12](https://github.com/ash-project/ash_postgres/compare/v1.3.11...v1.3.12) (2023-02-16) - -### Bug Fixes: - -- exclude `order_by` when building aggregates - -## [v1.3.11](https://github.com/ash-project/ash_postgres/compare/v1.3.10...v1.3.11) (2023-02-16) - -### Bug Fixes: - -- properly find migration directories in umbrella apps - -- don't double-cast to array for list aggregates - -### Improvements: - -- significantly optimize aggregate queries - -- better type casting for concat operator - -## [v1.3.10](https://github.com/ash-project/ash_postgres/compare/v1.3.9...v1.3.10) (2023-02-09) - -### Bug Fixes: - -- sorting on optimized first aggregates - -## [v1.3.9](https://github.com/ash-project/ash_postgres/compare/v1.3.8...v1.3.9) (2023-02-09) - -### Bug Fixes: - -- do limit/offset outside of query if distinct is required - -- load by **order** ascending - -### Improvements: - -- support new `uniq?` option on count/list aggregates - -- optimized `first` aggregates where possible - -## [v1.3.8](https://github.com/ash-project/ash_postgres/compare/v1.3.7...v1.3.8) (2023-02-06) - -### Bug Fixes: - -- Actually use `AshPostgres.Repo` behaviour (#129) - -### Improvements: - -- authorization filters are now attached by ash core - -## [v1.3.7](https://github.com/ash-project/ash_postgres/compare/v1.3.6...v1.3.7) (2023-02-06) - -### Bug Fixes: - -- Actually use `AshPostgres.Repo` behaviour (#129) - -### Improvements: - -- authorization filters are now attached by ash core - -## [v1.3.6](https://github.com/ash-project/ash_postgres/compare/v1.3.5...v1.3.6) (2023-02-03) - -### Bug Fixes: - -- properly set next migration name - -- override `insert` function for proper ecto interop - -### Improvements: - -- add `migration_ignore_attributes` - -## [v1.3.5](https://github.com/ash-project/ash_postgres/compare/v1.3.4...v1.3.5) (2023-01-29) - -### Bug Fixes: - -- properly convert to/from ecto, only when necessary - -## [v1.3.4](https://github.com/ash-project/ash_postgres/compare/v1.3.3...v1.3.4) (2023-01-28) - -### Bug Fixes: - -- support latest ecto interop changes in ash core - -### Improvements: - -- properly cast division to floats for elixir-y behavior - -- support for dynamically set repo - -- update ash - -## [v1.3.3](https://github.com/ash-project/ash_postgres/compare/v1.3.2...v1.3.3) (2023-01-18) - -### Improvements: - -- update to new docs patterns - -## [v1.3.2](https://github.com/ash-project/ash_postgres/compare/v1.3.1...v1.3.2) (2023-01-17) - -### Bug Fixes: - -- nest subqueries when required for distinct - -- replace `{:in, ...}` type with `{:array, ...}` - -## [v1.3.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0...v1.3.1) (2023-01-11) - -### Bug Fixes: - -- allow for non attribute aggregate references for first/list - -## [v1.3.0](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.4...v1.3.0) (2023-01-11) - -### Improvements: - -- update to latest ash - -## [v1.3.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.3...v1.3.0-rc.4) (2023-01-09) - -### Bug Fixes: - -- properly join to all required relationships - -## [v1.3.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.2...v1.3.0-rc.3) (2023-01-09) - -### Bug Fixes: - -- properly type cast in fragments (and elsewhere) - -## [v1.3.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.1...v1.3.0-rc.2) (2023-01-06) - -### Bug Fixes: - -- undo changes that caused type casting bugs - -## [v1.3.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.0...v1.3.0-rc.1) (2023-01-06) - -### Bug Fixes: - -- undo changes that caused type casting bugs - -## [v1.3.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.0...v1.3.0-rc.1) (2023-01-06) - -### Bug Fixes: - -- use `parent_expr` instead of `this` - -- various expression & type building fixes - -## [v1.3.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.2.6...v1.3.0-rc.0) (2023-01-04) - -### Features: - -- support latest ash - -### Bug Fixes: - -- honor calculation constraints - -- handle lists with expressions inside - -### Improvements: - -- support calc constraints - -- support new `cast_in_query?/2` - -- support calculations as aggregate targets - -## [v1.2.6](https://github.com/ash-project/ash_postgres/compare/v1.2.5...v1.2.6) (2022-12-27) - -### Bug Fixes: - -- properly set `migrations_path` default in umbrellas - -- don't subquery unless we have to - -## [v1.2.5](https://github.com/ash-project/ash_postgres/compare/v1.2.4...v1.2.5) (2022-12-21) - -### Bug Fixes: - -- don't group aggregates that reference relationships in their filters - -- properly skip unique indexes when configured - -### Improvements: - -- add like and ilike - -## [v1.2.4](https://github.com/ash-project/ash_postgres/compare/v1.2.3...v1.2.4) (2022-12-18) - -### Bug Fixes: - -- properly add aggregates to query when referenced from calculations - -### Improvements: - -- distinct on source of query, not relationship destination - -## [v1.2.3](https://github.com/ash-project/ash_postgres/compare/v1.2.2...v1.2.3) (2022-12-15) - -### Bug Fixes: - -- properly combine sort + to many join filter - -## [v1.2.2](https://github.com/ash-project/ash_postgres/compare/v1.2.1...v1.2.2) (2022-12-15) - -### Improvements: - -- udpate to latest ash, fix array issues - -## [v1.2.1](https://github.com/ash-project/ash_postgres/compare/v1.2.0...v1.2.1) (2022-12-13) - -### Bug Fixes: - -- pattern match error in `lazy_non_matching_defaults/1` - -- use attribute name not attribute for default funs - -- _actually_ fix `default_fun` upserts - -- fix upserting update_defaults - -## [v1.2.0](https://github.com/ash-project/ash_postgres/compare/v1.2.0-rc.1...v1.2.0) (2022-12-13) - -### Bug Fixes: - -- make migration generator work better for umbrellas - -## [v1.2.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.2.0-rc.0...v1.2.0-rc.1) (2022-12-10) - -### Bug Fixes: - -- don't make migration generation recursive - -- nevermind, can't make migrate recursive - -### Improvements: - -- make migrate task recursive as well - -- mark generate_migrations as recursive for umbrellas - -## [v1.2.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.1.3...v1.2.0-rc.0) (2022-12-10) - -### Features: - -- avg/min/max/custom aggregate support - -### Bug Fixes: - -- various broken behavior from new aggregate work - -- forgot a - -- fix various problems with the model behind aggregates - -- properly set binding names for many to many join filters - -### Improvements: - -- better error messages from mix tasks - -- validate that references refer to relationships - -- avg/min/max/custom aggregate support - -- upgrade and depend on ash version - -- fix lateral many to many joins - -- inform users about postgres incompatibility with multidimensional arrays - -## [v1.1.3](https://github.com/ash-project/ash_postgres/compare/v1.1.2...v1.1.3) (2022-12-01) - -### Bug Fixes: - -- properly turn custom index keys into atoms - -### Improvements: - -- update ash, add test for transaction hooks - -- support new transaction info with hooks - -- add unique constraints to changeset for custom unique indexes - -- separate out concurrent index creations and do them in a separate transaction - -## [v1.1.2](https://github.com/ash-project/ash_postgres/compare/v1.1.1...v1.1.2) (2022-11-21) - -### Bug Fixes: - -- don't use hard-coded join assoc name (#118) - -### Improvements: - -- add `migration_defaults` for customizing default values - -## [v1.1.1](https://github.com/ash-project/ash_postgres/compare/v1.1.0...v1.1.1) (2022-10-25) - -### Bug Fixes: - -- && operator in expressions to point to ash_elixir_and (#115) - -### Improvements: - -- add check for unsupported expression - -## [v1.1.0](https://github.com/ash-project/ash_postgres/compare/v1.0.0...v1.1.0) (2022-10-20) - -### Features: - -- support `now()` in latest Ash - -## [v1.0.0](https://github.com/ash-project/ash_postgres/compare/v0.43.0...v1.0.0) (2022-10-17) - -### Bug Fixes: - -- no unnecessary type cast on count/sum aggregates - -- don't apply `filter` to `array_agg` - -### Improvements: - -- update to Ash 2.0 - -- handle UUID types better - -- set lateral join source for latest ash - -- use `prepend?: true` option when applying relationship sorts - -## [v1.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.8...v1.0.0-rc.9) (2022-10-07) - -### Bug Fixes: - -- handle custom calculation selects properly - -- use attribute source for identity fields - -### Improvements: - -- update to the latest ash - -- remove the need to dynamically expand fragments - -- when casting string to uuid, dump to binary - -- update to latest ash - -## [v1.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.7...v1.0.0-rc.8) (2022-09-29) - -### Bug Fixes: - -- never attempt to group custom operations - -- wrap case statement in parens - -### Improvements: - -- `exists` filters necessitate multiple aggregate joins (for now) - -## [v1.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.6...v1.0.0-rc.7) (2022-09-28) - -### Bug Fixes: - -- properly type cast top level fragments - -### Improvements: - -- update to the latest ash - -- upgrade to new `exists` usage - -## [v1.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.5...v1.0.0-rc.6) (2022-09-21) - -### Improvements: - -- support latest ash - -## [v1.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.4...v1.0.0-rc.5) (2022-09-15) - -### Improvements: - -- update to latest ash - -- implement Length function (#111) - -- upgrade to latest ash - -## [v1.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.3...v1.0.0-rc.4) (2022-09-14) - -### Improvements: - -- support latest ash - -- support manual relationships with joins - -## [v1.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.2...v1.0.0-rc.3) (2022-09-12) - -### Bug Fixes: - -- keep unique index keys in order in migrations - -## [v1.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.1...v1.0.0-rc.2) (2022-09-06) - -### Improvements: - -- support latest ash `exists/2` expr - -## [v1.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.0...v1.0.0-rc.1) (2022-09-04) - -## [v0.43.0](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.7...v0.43.0) (2022-08-05) - -### Bug Fixes: - -- properly order check constraints - -- remove check constraints before adding them - -### Improvements: - -- fix typecasting for calculations & embed access - -- add custom_statements to migration generator - -- support `||` and `&&` - -## [v0.42.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.6...v0.42.0-rc.7) (2022-07-14) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- use new doc_index patterns - -- support upsert_identity with base_filter - -- support upsert_identity with base filters - -- handle various join bugs - -- use attribute.name if attribute.source is nil - -- set attribute source properly - -- ensure source is always set on attributes in snapshots - -- handle paths for aggregates w/ > 2 relationships - -- rename attributes correctly in down migration (#98) - -- don't generate modify commands for attributes due to schema changes - -- default schema to primary schema - -- test and confirm behavior of schemas - -- use correct bindings for filtered relationships - -- cast calcs in query expressions - -- explicitly type cast aggregate/calc selects - -- don't try and match reference schema to table schema - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- add default guide, and empty ash postgres guide - -- set `update_defaults` on upsert results - -- handle fallback ecto migration default elegantly (#94) - -- add `ignore?` option to `references` - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.5...v0.42.0-rc.6) (2022-07-10) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- use new doc_index patterns - -- support upsert_identity with base_filter - -- support upsert_identity with base filters - -- handle various join bugs - -- use attribute.name if attribute.source is nil - -- set attribute source properly - -- ensure source is always set on attributes in snapshots - -- handle paths for aggregates w/ > 2 relationships - -- rename attributes correctly in down migration (#98) - -- don't generate modify commands for attributes due to schema changes - -- default schema to primary schema - -- test and confirm behavior of schemas - -- use correct bindings for filtered relationships - -- cast calcs in query expressions - -- explicitly type cast aggregate/calc selects - -- don't try and match reference schema to table schema - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- set `update_defaults` on upsert results - -- handle fallback ecto migration default elegantly (#94) - -- add `ignore?` option to `references` - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.4...v0.42.0-rc.5) (2022-07-06) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- support upsert_identity with base_filter - -- support upsert_identity with base filters - -- handle various join bugs - -- use attribute.name if attribute.source is nil - -- set attribute source properly - -- ensure source is always set on attributes in snapshots - -- handle paths for aggregates w/ > 2 relationships - -- rename attributes correctly in down migration (#98) - -- don't generate modify commands for attributes due to schema changes - -- default schema to primary schema - -- test and confirm behavior of schemas - -- use correct bindings for filtered relationships - -- cast calcs in query expressions - -- explicitly type cast aggregate/calc selects - -- don't try and match reference schema to table schema - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- set `update_defaults` on upsert results. For most users, this means that where previously `updated_at` would not get set on an upsert that ultimately resulted in an update, it will now. - -- handle fallback ecto migration default elegantly (#94) - -- add `ignore?` option to `references` - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.3...v0.42.0-rc.4) (2022-06-28) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- use attribute.name if attribute.source is nil - -- set attribute source properly - -- ensure source is always set on attributes in snapshots - -- handle paths for aggregates w/ > 2 relationships - -- rename attributes correctly in down migration (#98) - -- don't generate modify commands for attributes due to schema changes - -- default schema to primary schema - -- test and confirm behavior of schemas - -- use correct bindings for filtered relationships - -- cast calcs in query expressions - -- explicitly type cast aggregate/calc selects - -- don't try and match reference schema to table schema - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- handle fallback ecto migration default elegantly (#94) - -- add `ignore?` option to `references` - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.2...v0.42.0-rc.3) (2022-06-28) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- set attribute source properly - -- ensure source is always set on attributes in snapshots - -- handle paths for aggregates w/ > 2 relationships - -- rename attributes correctly in down migration (#98) - -- don't generate modify commands for attributes due to schema changes - -- default schema to primary schema - -- test and confirm behavior of schemas - -- use correct bindings for filtered relationships - -- cast calcs in query expressions - -- explicitly type cast aggregate/calc selects - -- don't try and match reference schema to table schema - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- handle fallback ecto migration default elegantly (#94) - -- add `ignore?` option to `references` - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.1...v0.42.0-rc.2) (2022-05-18) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- don't try and match reference schema to table schema - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.0...v0.42.0-rc.1) (2022-05-18) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- don't use `table` where we should use `schema` in migration generator - -- handle combinations of distinct & sort - -- ensure all single actions are explicitly marked as primary? (#95) - -- only rename schema when necessary - -- inspect un-defaultable value in error message - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- check_migrations, rename to `--check` - -- add explicit timeout capability declaration - -- add static schema specification in DSL - -- support static schema specification in migration generator - -- implement decimal ecto migration default (#91) - -- support float as Ecto migration default (#89) - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.42.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.41.7...v0.42.0-rc.0) (2022-04-26) - -### Features: - -- support `cast_in_query?/0` and `source` - -### Bug Fixes: - -- select custom aggregates properly - -- don't add reference when renaming column if unnecessary - -- don't cast `nil` to `""` - -- `!is_atom/1` -> `!is_boolean/1` - -- sanitize lists to stringify atoms - -- cast embedded atoms to strings first - -- don't cast `{:in, :any}` types - -- more don't cast any types - -- don't cast if there is no type - -- properly handle relationship filter bindings - -- don't consider fields changed with only source -> name changes - -- handle name -> source change in more places - -- handle name -> source rename in operation ordering - -- fix aggregate/base filters - -- don't select more fields than necessary - -- don't call `ecto_type` twice when resolving types - -- place expressions in the proper order in selects - -- match on count in expr - -- remove incorrect param count tracking - -- properly track param count - -- properly reverse parameters before/after expansion - -- don't use the base ecto type - -- don't sort when joining - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- update ecto - -- add atom impl for `EctoMigrationDefault` - -- Add EctoMigrationDefault protocol and implement defaults (#87) - -- update ecto, fix dialyzer - -- support new timeouts - -- make select unique before running query - -- add doc_index - -- add exclusion_constraint_names (#83) - -- support referencing aggregates from aggregate filters - -- support access syntax - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.7](https://github.com/ash-project/ash_postgres/compare/v0.41.6...v0.41.7) (2021-12-21) - -### Bug Fixes: - -- ensure repo is compiled (#80) - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.6](https://github.com/ash-project/ash_postgres/compare/v0.41.5...v0.41.6) (2021-12-21) - -### Bug Fixes: - -- properly construct nested join relationships - -- use `CiStringWrapper` type in ash_postgres - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.5](https://github.com/ash-project/ash_postgres/compare/v0.41.4...v0.41.5) (2021-11-26) - -### Bug Fixes: - -- ensure we are returning \* on upserts (#79) - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.4](https://github.com/ash-project/ash_postgres/compare/v0.41.3...v0.41.4) (2021-11-25) - -### Bug Fixes: - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- don't upsert defaults on conflict (#77) - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.3](https://github.com/ash-project/ash_postgres/compare/v0.41.2...v0.41.3) (2021-11-13) - -### Bug Fixes: - -- handle new if types - -- copy query prefix to newly created query (#74) - -### Improvements: - -- relax ash version requirement - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.2](https://github.com/ash-project/ash_postgres/compare/v0.41.1...v0.41.2) (2021-11-10) - -### Bug Fixes: - -- copy query prefix to newly created query (#74) - -### Improvements: - -- add custom migration types, and repo level override - -- update to latest version of ash - -## [v0.41.1](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.9...v0.41.1) (2021-11-03) - -### Bug Fixes: - -- copy query prefix to newly created query (#74) - -### Improvements: - -- update to latest version of ash - -## [v0.41.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.8...v0.41.0-rc.9) (2021-11-01) - -### Bug Fixes: - -- use proper ecto types everywhere - -- try to fix missing paren issue in array_agg - -- fix can? for :joins (#73) - -- remove unused default value - -- use proper identity names for polymorphic resources - -- set identity names propertly for polymorphic resources - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `default` on aggregates - -- support `custom_indexes` - -## [v0.41.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.7...v0.41.0-rc.8) (2021-10-25) - -### Bug Fixes: - -- fix can? for :joins (#73) - -- remove unused default value - -- use proper identity names for polymorphic resources - -- set identity names propertly for polymorphic resources - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `default` on aggregates - -- support `custom_indexes` - -## [v0.41.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.6...v0.41.0-rc.7) (2021-10-24) - -### Bug Fixes: - -- fix can? for :joins (#73) - -- remove unused default value - -- use proper identity names for polymorphic resources - -- set identity names propertly for polymorphic resources - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.5...v0.41.0-rc.6) (2021-09-26) - -### Bug Fixes: - -- remove unused default value - -- use proper identity names for polymorphic resources - -- set identity names propertly for polymorphic resources - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.4...v0.41.0-rc.5) (2021-09-21) - -### Bug Fixes: - -- use proper identity names for polymorphic resources - -- set identity names propertly for polymorphic resources - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.3...v0.41.0-rc.4) (2021-09-21) - -### Bug Fixes: - -- set identity names propertly for polymorphic resources - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.2...v0.41.0-rc.3) (2021-09-21) - -### Bug Fixes: - -- handle nil values in snapshots better - -- remove unused field from snapshot parsing - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.1...v0.41.0-rc.2) (2021-09-21) - -### Bug Fixes: - -- remove unused field from snapshot parsing - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.0...v0.41.0-rc.1) (2021-09-20) - -### Improvements: - -- support `custom_indexes` - -## [v0.41.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.40.11...v0.41.0-rc.0) (2021-09-13) - -### Breaking Changes: - -- update to latest ash/ecto versions w/ parameterized types - -### Improvements: - -- Support default tenant migration path in releases (#69) - -## [v0.40.11](https://github.com/ash-project/ash_postgres/compare/v0.40.10...v0.40.11) (2021-07-28) - -### Bug Fixes: - -- set subquery prefix properly - -## [v0.40.10](https://github.com/ash-project/ash_postgres/compare/v0.40.9...v0.40.10) (2021-07-27) - -### Bug Fixes: - -- set subquery source correctly - -- create parameter for ci strings - -- explicitly set prefix at each level - -- interaction w/ attribute and context tenancy - -### Improvements: - -- info on migration generator output - -- use match: :full on attr multitenancy - -- update to latest ash - -- update to latest ash - -- upgrade ash dep - -## [v0.40.9](https://github.com/ash-project/ash_postgres/compare/v0.40.8...v0.40.9) (2021-07-22) - -### Bug Fixes: - -- don't add a non-list to a list - -### Improvements: - -- add sort + select test - -## [v0.40.8](https://github.com/ash-project/ash_postgres/compare/v0.40.7...v0.40.8) (2021-07-19) - -### Bug Fixes: - -- ensure source table is sorted in lateral join - -### Improvements: - -- fix significant performance issue in lateral joins - -## [v0.40.7](https://github.com/ash-project/ash_postgres/compare/v0.40.6...v0.40.7) (2021-07-12) - -### Improvements: - -- support default_prefix configuration - -## [v0.40.6](https://github.com/ash-project/ash_postgres/compare/v0.40.5...v0.40.6) (2021-07-08) - -### Bug Fixes: - -- fix migrator mix tasks w/ only/except tenants - -- drop foreign keys after table create properly - -- drop foreign keys before dropping table - -- left_lateral_join for many_to_many aggregates - -- properly reference nested aggregate fields for join - -- properly determine fallback table for polymorphic resources - -- ensure non-tenant resources can be aggregates - -- properly set aggregate query sources - -- retain parent as bindings - -- don't add `rel_source` at all - -- properly build atoms list - -- horribly hack ecto for dynamic bindings - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- `--name` when generating migrations - -- add `mix ash_postgres.rollback` - -- update to latest ash - -- update to latest ash - -- leverage new `private_vars` for errs - -## [v0.40.5](https://github.com/ash-project/ash_postgres/compare/v0.40.4...v0.40.5) (2021-07-08) - -### Bug Fixes: - -- fix migrator mix tasks w/ only/except tenants - -- drop foreign keys after table create properly - -- drop foreign keys before dropping table - -- left_lateral_join for many_to_many aggregates - -- properly reference nested aggregate fields for join - -- properly determine fallback table for polymorphic resources - -- ensure non-tenant resources can be aggregates - -- properly set aggregate query sources - -- retain parent as bindings - -- don't add `rel_source` at all - -- properly build atoms list - -- horribly hack ecto for dynamic bindings - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- add `mix ash_postgres.rollback` - -- update to latest ash - -- update to latest ash - -- leverage new `private_vars` for errs - -## [v0.40.4](https://github.com/ash-project/ash_postgres/compare/v0.40.3...v0.40.4) (2021-07-05) - -### Bug Fixes: - -- left_lateral_join for many_to_many aggregates - -- properly reference nested aggregate fields for join - -- properly determine fallback table for polymorphic resources - -- ensure non-tenant resources can be aggregates - -- properly set aggregate query sources - -- retain parent as bindings - -- don't add `rel_source` at all - -- properly build atoms list - -- horribly hack ecto for dynamic bindings - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- update to latest ash - -- update to latest ash - -- leverage new `private_vars` for errs - -## [v0.40.3](https://github.com/ash-project/ash_postgres/compare/v0.40.2...v0.40.3) (2021-07-03) - -### Bug Fixes: - -- ensure non-tenant resources can be aggregates - -- properly set aggregate query sources - -- retain parent as bindings - -- don't add `rel_source` at all - -- properly build atoms list - -- horribly hack ecto for dynamic bindings - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- update to latest ash - -- leverage new `private_vars` for errs - -## [v0.40.2](https://github.com/ash-project/ash_postgres/compare/v0.40.1...v0.40.2) (2021-07-02) - -### Bug Fixes: - -- properly set aggregate query sources - -- retain parent as bindings - -- don't add `rel_source` at all - -- properly build atoms list - -- horribly hack ecto for dynamic bindings - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- update to latest ash - -- leverage new `private_vars` for errs - -## [v0.40.1](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc5...v0.40.1) (2021-07-02) - -### Bug Fixes: - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- update to latest ash - -- leverage new `private_vars` for errs - -## [v0.40.0-rc5](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc4...v0.40.0-rc5) (2021-07-01) - -### Bug Fixes: - -- properly coalesce aggregate values - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- leverage new `private_vars` for errs - -## [v0.40.0-rc4](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc3...v0.40.0-rc4) (2021-06-23) - -### Bug Fixes: - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -### Improvements: - -- leverage new `private_vars` for errs - -## [v0.40.0-rc3](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc2...v0.40.0-rc3) (2021-06-15) - -### Bug Fixes: - -- always add nullability flag - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -## [v0.40.0-rc2](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc1...v0.40.0-rc2) (2021-06-08) - -### Bug Fixes: - -- sort references only after other same-table ops - -- generate multitenant foreign keys properly - -## [v0.40.0-rc1](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc.0...v0.40.0-rc1) (2021-06-05) - -## [v0.39.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.38.11...v0.39.0-rc.0) (2021-06-04) - -### Features: - -- support expression based calculations - -- support concat + if expressions - -### Improvements: - -- various other improvements - -## [v0.38.11](https://github.com/ash-project/ash_postgres/compare/v0.38.10...v0.38.11) (2021-05-23) - -### Bug Fixes: - -- set prefix to "public" for fkeys to public schema - -### Improvements: - -- set explicit prefix on join filters - -## [v0.38.10](https://github.com/ash-project/ash_postgres/compare/v0.38.9...v0.38.10) (2021-05-19) - -### Improvements: - -- support new ash upsert specifying targets - -- update to latest ash - -## [v0.38.9](https://github.com/ash-project/ash_postgres/compare/v0.38.8...v0.38.9) (2021-05-12) - -### Bug Fixes: - -- properly group many_to_many aggregates - -## [v0.38.8](https://github.com/ash-project/ash_postgres/compare/v0.38.7...v0.38.8) (2021-05-09) - -### Improvements: - -- update to the latest ash version - -## [v0.38.7](https://github.com/ash-project/ash_postgres/compare/v0.38.6...v0.38.7) (2021-05-09) - -### Improvements: - -- support latest ash/filtering on related aggregates - -## [v0.38.6](https://github.com/ash-project/ash_postgres/compare/v0.38.5...v0.38.6) (2021-05-07) - -### Bug Fixes: - -- properly construct sources for lateral joins - -- copy the correct data for lateral join queries - -- better errors in error cases - -### Improvements: - -- update to latest ash - -## [v0.38.5](https://github.com/ash-project/ash_postgres/compare/v0.38.4...v0.38.5) (2021-05-07) - -### Bug Fixes: - -- don't cast booleans to string in last_ditch_cast - -## [v0.38.4](https://github.com/ash-project/ash_postgres/compare/v0.38.3...v0.38.4) (2021-05-07) - -### Improvements: - -- support latest ash version resource sorts - -## [v0.38.3](https://github.com/ash-project/ash_postgres/compare/v0.38.2...v0.38.3) (2021-05-06) - -### Improvements: - -- update to latest ash - -- document script to iterate migrations (#65) - -## [v0.38.2](https://github.com/ash-project/ash_postgres/compare/v0.38.1...v0.38.2) (2021-05-04) - -### Bug Fixes: - -- join to join table in lateral join query - -- multitenancy + lateral join sources - -- don't distinct in lateral joins - -## [v0.38.1](https://github.com/ash-project/ash_postgres/compare/v0.38.0...v0.38.1) (2021-05-04) - -### Bug Fixes: - -- fix fragment processing broken (#64) - -## [v0.38.0](https://github.com/ash-project/ash_postgres/compare/v0.37.8...v0.38.0) (2021-04-29) - -### Features: - -- support new side load improvements - -### Improvements: - -- Preserve attribute order (#63) - -## [v0.37.8](https://github.com/ash-project/ash_postgres/compare/v0.37.7...v0.37.8) (2021-04-27) - -### Bug Fixes: - -- simpler index names - -- don't prefix unique indices with prefix() - -- sort index operations last - -### Improvements: - -- custom index names - -## [v0.37.7](https://github.com/ash-project/ash_postgres/compare/v0.37.6...v0.37.7) (2021-04-27) - -### Bug Fixes: - -- remove inspects that were left in by accident - -## [v0.37.6](https://github.com/ash-project/ash_postgres/compare/v0.37.5...v0.37.6) (2021-04-27) - -### Bug Fixes: - -- type cast atoms to strings in last ditch cast - -- properly type cast - -- Remove duplicate file extension (#60) - -## [v0.37.5](https://github.com/ash-project/ash_postgres/compare/v0.37.4...v0.37.5) (2021-04-27) - -### Bug Fixes: - -- properly type cast - -## [v0.37.4](https://github.com/ash-project/ash_postgres/compare/v0.37.3...v0.37.4) (2021-04-26) - -### Improvements: - -- support `list` aggregate - -## [v0.37.3](https://github.com/ash-project/ash_postgres/compare/v0.37.2...v0.37.3) (2021-04-26) - -### Bug Fixes: - -- stringify struct defaults in migration generator - -- properly comment out extension uninstallation code - -## [v0.37.2](https://github.com/ash-project/ash_postgres/compare/v0.37.1...v0.37.2) (2021-04-21) - -### Improvements: - -- support ash enums - -## [v0.37.1](https://github.com/ash-project/ash_postgres/compare/v0.37.0...v0.37.1) (2021-04-19) - -### Bug Fixes: - -- include type in references (because it is _not_ automatic) - -## [v0.37.0](https://github.com/ash-project/ash_postgres/compare/v0.36.5...v0.37.0) (2021-04-19) - -### Features: - -- add check_constraints, both for validation and migrations - -## [v0.36.5](https://github.com/ash-project/ash_postgres/compare/v0.36.4...v0.36.5) (2021-04-13) - -### Bug Fixes: - -- always drop constraints before modifying - -- properly compare old references and new references - -## [v0.36.4](https://github.com/ash-project/ash_postgres/compare/v0.36.3...v0.36.4) (2021-04-12) - -### Bug Fixes: - -- don't explicitly set type in `references` - -### Improvements: - -- default integers to `:bigint` - -## [v0.36.3](https://github.com/ash-project/ash_postgres/compare/v0.36.2...v0.36.3) (2021-04-12) - -### Improvements: - -- primary autoincrement key as bigserial (#54) - -## [v0.36.2](https://github.com/ash-project/ash_postgres/compare/v0.36.1...v0.36.2) (2021-04-09) - -### Improvements: - -- support new ash select feature - -## [v0.36.1](https://github.com/ash-project/ash_postgres/compare/v0.36.0...v0.36.1) (2021-04-04) - -### Bug Fixes: - -- raise when `all_tenants/0` default impl is called - -### Improvements: - -- add sum aggregate (#53) - -## [v0.36.0](https://github.com/ash-project/ash_postgres/compare/v0.35.5...v0.36.0) (2021-04-01) - -### Features: - -- support configuring references - -- support configuring polymorphic references - -- support `distinct` Ash queries - -## [v0.35.5](https://github.com/ash-project/ash_postgres/compare/v0.35.4...v0.35.5) (2021-03-29) - -### Bug Fixes: - -- Made AshPostgres.Repo.init/2 overridable (#51) - -### Improvements: - -- only count resources w/ create action for nullability - -- better error message on missing table - -## [v0.35.4](https://github.com/ash-project/ash_postgres/compare/v0.35.3...v0.35.4) (2021-03-21) - -### Bug Fixes: - -- reroute `Ash.Type.UUID` to `:uuid` in migrations - -- force create extensions snapshot - -### Improvements: - -- consistent foreign key names - -- support custom foreign key error messages - -## [v0.35.3](https://github.com/ash-project/ash_postgres/compare/v0.35.2...v0.35.3) (2021-03-19) - -### Bug Fixes: - -- force create extensions snapshot - -- more conservative inner join checks - -- add back in inner join detection logic - -### Improvements: - -- consistent foreign key names - -- support custom foreign key error messages - -## [v0.35.2](https://github.com/ash-project/ash_postgres/compare/v0.35.1...v0.35.2) (2021-03-05) - -### Bug Fixes: - -- more conservative inner join checks - -- add back in inner join detection logic - -## [v0.35.1](https://github.com/ash-project/ash_postgres/compare/v0.35.0...v0.35.1) (2021-03-02) - -### Bug Fixes: - -- don't start the whole app in migrate - -## [v0.35.0](https://github.com/ash-project/ash_postgres/compare/v0.34.7...v0.35.0) (2021-03-02) - -### Features: - -- automatically install extensions from repo - -## [v0.34.7](https://github.com/ash-project/ash_postgres/compare/v0.34.6...v0.34.7) (2021-03-02) - -### Bug Fixes: - -- typo in references for multitenancy - -- `null: true` when attr isn't on all resources for a table - -## [v0.34.6](https://github.com/ash-project/ash_postgres/compare/v0.34.5...v0.34.6) (2021-02-24) - -### Bug Fixes: - -- better embedded filters, switch to latest ash - -## [v0.34.5](https://github.com/ash-project/ash_postgres/compare/v0.34.4...v0.34.5) (2021-02-23) - -### Improvements: - -- support latest ash - -## [v0.34.4](https://github.com/ash-project/ash_postgres/compare/v0.34.3...v0.34.4) (2021-02-08) - -### Bug Fixes: - -- trim when choosing new attribute name - -## [v0.34.3](https://github.com/ash-project/ash_postgres/compare/v0.34.2...v0.34.3) (2021-02-06) - -### Bug Fixes: - -- don't reference polymorphic tables to belongs_to relationships - -## [v0.34.2](https://github.com/ash-project/ash_postgres/compare/v0.34.1...v0.34.2) (2021-02-06) - -### Bug Fixes: - -- set up references properly - -## [v0.34.1](https://github.com/ash-project/ash_postgres/compare/v0.34.0...v0.34.1) (2021-02-06) - -### Bug Fixes: - -- reference the configured table if set - -## [v0.34.0](https://github.com/ash-project/ash_postgres/compare/v0.33.1...v0.34.0) (2021-02-06) - -### Features: - -- support polymorphic relationships - -## [v0.33.1](https://github.com/ash-project/ash_postgres/compare/v0.33.0...v0.33.1) (2021-01-27) - -### Bug Fixes: - -- actually insert the tracking row - -## [v0.33.0](https://github.com/ash-project/ash_postgres/compare/v0.32.2...v0.33.0) (2021-01-27) - -### Features: - -- add `mix ash_postgres.create` - -- add `mix ash_postgres.migrate` - -- add `mix ash_postgres.migrate --tenants` - -- add `mix ash_postgres.drop` - -### Bug Fixes: - -- rework the way multitenant migrations work - -## [v0.32.2](https://github.com/ash-project/ash_postgres/compare/v0.32.1...v0.32.2) (2021-01-26) - -### Bug Fixes: - -- un-break the `in` filter type casting code - -### Improvements: - -- better errors for multitenant unique constraints - -## [v0.32.1](https://github.com/ash-project/ash_postgres/compare/v0.32.0...v0.32.1) (2021-01-24) - -### Bug Fixes: - -- `ago` was adding, not subtracting - -## [v0.32.0](https://github.com/ash-project/ash_postgres/compare/v0.31.1...v0.32.0) (2021-01-24) - -### Features: - -- support latest ash + contains - -## [v0.31.1](https://github.com/ash-project/ash_postgres/compare/v0.31.0...v0.31.1) (2021-01-22) - -### Improvements: - -- update to latest ash - -## [v0.31.0](https://github.com/ash-project/ash_postgres/compare/v0.30.1...v0.31.0) (2021-01-22) - -### Features: - -- support fragments - -- support type casting - -- update to latest ash to support expressions - -### Bug Fixes: - -- update CI versions - -## [v0.30.1](https://github.com/ash-project/ash_postgres/compare/v0.30.0...v0.30.1) (2021-01-13) - -## [v0.30.0](https://github.com/ash-project/ash_postgres/compare/v0.29.6...v0.30.0) (2021-01-13) - -### Features: - -- Add check_migrated option to migration generator (#40) (#43) - -## [v0.29.6](https://github.com/ash-project/ash_postgres/compare/v0.29.5...v0.29.6) (2021-01-12) - -### Bug Fixes: - -- rename out of phase, small migration fix - -## [v0.29.5](https://github.com/ash-project/ash_postgres/compare/v0.29.4...v0.29.5) (2021-01-10) - -### Improvements: - -- Use ecto_sql formatter settings (#38) - -## [v0.29.4](https://github.com/ash-project/ash_postgres/compare/v0.29.3...v0.29.4) (2021-01-10) - -### Improvements: - -- Omit field opts if they are default values (#37) - -## [v0.29.3](https://github.com/ash-project/ash_postgres/compare/v0.29.2...v0.29.3) (2021-01-08) - -### Improvements: - -- support latest ash - -## [v0.29.2](https://github.com/ash-project/ash_postgres/compare/v0.29.1...v0.29.2) (2021-01-08) - -### Improvements: - -- Make integer serial if generated - -## [v0.29.1](https://github.com/ash-project/ash_postgres/compare/v0.29.0...v0.29.1) (2021-01-08) - -### Improvements: - -- support latest ash version - -## [v0.29.0](https://github.com/ash-project/ash_postgres/compare/v0.28.1...v0.29.0) (2021-01-08) - -### Features: - -- retain snapshot history - -### Improvements: - -- support latest ash version - -## [v0.28.1](https://github.com/ash-project/ash_postgres/compare/v0.28.0...v0.28.1) (2021-01-07) - -### Improvements: - -- Add :binary migration type (#33) - -## [v0.28.0](https://github.com/ash-project/ash_postgres/compare/v0.27.0...v0.28.0) (2020-12-29) - -### Features: - -- support latest Ash version - -## [v0.27.0](https://github.com/ash-project/ash_postgres/compare/v0.26.2...v0.27.0) (2020-12-23) - -### Features: - -- support refs on both sides of operators - -### Bug Fixes: - -- bump ash version - -## [v0.26.2](https://github.com/ash-project/ash_postgres/compare/v0.26.1...v0.26.2) (2020-12-06) - -### Bug Fixes: - -- properly accept the `tenant_migration_path` - -## [v0.26.1](https://github.com/ash-project/ash_postgres/compare/v0.26.0...v0.26.1) (2020-12-01) - -### Bug Fixes: - -- set default properly when modifying - -## [v0.26.0](https://github.com/ash-project/ash_postgres/compare/v0.25.5...v0.26.0) (2020-11-25) - -### Features: - -- don't drop columns unless explicitly told to - -### Bug Fixes: - -- various migration generator bug fixes - -## [v0.25.5](https://github.com/ash-project/ash_postgres/compare/v0.25.4...v0.25.5) (2020-11-17) - -### Bug Fixes: - -- drop constraints outside of phases (#29) - -## [v0.25.4](https://github.com/ash-project/ash_postgres/compare/v0.25.3...v0.25.4) (2020-11-07) - -### Bug Fixes: - -- only alter the things that have changed - -## [v0.25.3](https://github.com/ash-project/ash_postgres/compare/v0.25.2...v0.25.3) (2020-11-06) - -### Improvements: - -- add utc_datetime migration type - -## [v0.25.2](https://github.com/ash-project/ash_postgres/compare/v0.25.1...v0.25.2) (2020-11-03) - -### Bug Fixes: - -- access data_layer_query with function - -## [v0.25.1](https://github.com/ash-project/ash_postgres/compare/v0.25.0...v0.25.1) (2020-10-29) - -### Improvements: - -- mark repo as not requiring compile-time dep - -## [v0.25.0](https://github.com/ash-project/ash_postgres/compare/v0.24.0...v0.25.0) (2020-10-29) - -### Features: - -- multitenancy (#25) - -### Bug Fixes: - -- verify repo using ensure_compiled - -## [v0.24.0](https://github.com/ash-project/ash_postgres/compare/v0.23.2...v0.24.0) (2020-10-17) - -### Features: - -- support latest ash - -## [v0.23.2](https://github.com/ash-project/ash_postgres/compare/v0.23.1...v0.23.2) (2020-10-07) - -## [v0.23.1](https://github.com/ash-project/ash_postgres/compare/v0.23.0...v0.23.1) (2020-10-06) - -## [v0.23.0](https://github.com/ash-project/ash_postgres/compare/v0.22.1...v0.23.0) (2020-10-06) - -### Features: - -- update to latest ash, trigram filter - -## [v0.22.1](https://github.com/ash-project/ash_postgres/compare/v0.22.0...v0.22.1) (2020-10-01) - -### Bug Fixes: - -- don't group alters with creates (#22) - -- add jason dependency, clean lockfile (#21) - -## [v0.22.0](https://github.com/ash-project/ash_postgres/compare/v0.21.0...v0.22.0) (2020-09-24) - -### Features: - -- fix error when filtering with `true` - -### Bug Fixes: - -- broken types for `in` operator - -## [v0.21.0](https://github.com/ash-project/ash_postgres/compare/v0.20.1...v0.21.0) (2020-09-19) - -### Features: - -- support base_filter (#18) - -## [v0.20.1](https://github.com/ash-project/ash_postgres/compare/v0.20.0...v0.20.1) (2020-09-11) - -### Bug Fixes: - -- document/update migration path logic - -## [v0.20.0](https://github.com/ash-project/ash_postgres/compare/v0.19.0...v0.20.0) (2020-09-11) - -### Features: - -- snapshot-based migration generator - -## [v0.19.0](https://github.com/ash-project/ash_postgres/compare/v0.18.0...v0.19.0) (2020-09-02) - -### Features: - -- support inner joins when possible (#15) - -### Bug Fixes: - -- better support for aggregates/calculations when delegating - -- don't fail w/ no extensions configured - -## [v0.18.0](https://github.com/ash-project/ash_postgres/compare/v0.17.0...v0.18.0) (2020-08-26) - -### Features: - -- update to ash 1.11 (#13) - -- support Ash v1.10 (#12) - -- support latest ash - -- update to latest ash - -## [v0.17.0](https://github.com/ash-project/ash_postgres/compare/v0.16.1...v0.17.0) (2020-08-26) - -### Features: - -- update to ash 1.11 (#13) - -- support Ash v1.10 (#12) - -- support latest ash - -- update to latest ash - -## [v0.16.1](https://github.com/ash-project/ash_postgres/compare/v0.16.0...v0.16.1) (2020-08-19) - -### Bug Fixes: - -- fix compile/dialyzer issues - -## [v0.16.0](https://github.com/ash-project/ash_postgres/compare/v0.15.0...v0.16.0) (2020-08-19) - -### Features: - -- update to latest ash - -- update to latest version of ash - -## [v0.15.0](https://github.com/ash-project/ash_postgres/compare/v0.14.0...v0.15.0) (2020-08-18) - -### Features: - -- update to latest version of ash - -## [v0.14.0](https://github.com/ash-project/ash_postgres/compare/v0.13.0...v0.14.0) (2020-08-17) - -### Features: - -- support ash 1.7 - -- support named aggregates - -## [v0.13.0](https://github.com/ash-project/ash_postgres/compare/v0.12.1...v0.13.0) (2020-07-25) - -### Features: - -- update to latest ash - -- support latest ash - -## [v0.12.1](https://github.com/ash-project/ash_postgres/compare/v0.12.0...v0.12.1) (2020-07-24) - -### Bug Fixes: - -- add can? for `:aggregate` - -## [v0.12.0](https://github.com/ash-project/ash_postgres/compare/0.11.2...v0.12.0) (2020-07-24) - -### Features: - -- update to latest ash - -## [v0.11.2](https://github.com/ash-project/ash_postgres/compare/0.11.1...v0.11.2) (2020-07-23) - -### Bug Fixes: - -## [v0.11.1](https://github.com/ash-project/ash_postgres/compare/0.11.0...v0.11.1) (2020-07-23) - -### Bug Fixes: - -## [v0.11.0](https://github.com/ash-project/ash_postgres/compare/0.10.0...v0.11.0) (2020-07-23) - -### Features: - -- support ash 13.0 aggregates - -## [v0.10.0](https://github.com/ash-project/ash_postgres/compare/0.9.0...v0.10.0) (2020-07-15) - -### Features: - -- update to latest ash - -## [v0.9.0](https://github.com/ash-project/ash_postgres/compare/0.8.0...v0.9.0) (2020-07-13) - -### Features: - -- update to latest ash - -## [v0.8.0](https://github.com/ash-project/ash_postgres/compare/0.7.0...v0.8.0) (2020-07-09) - -### Features: - -- update to latest ash - -## [v0.7.0](https://github.com/ash-project/ash_postgres/compare/0.6.0...v0.7.0) (2020-07-09) - -### Features: - -- update to latest ash - -- update to latest ash, add docs - -- update to ash 0.9.1 for transactions - -## [v0.6.0](https://github.com/ash-project/ash_postgres/compare/0.5.0...v0.6.0) (2020-06-29) - -### Features: - -- update to latest ash - -## [v0.5.0](https://github.com/ash-project/ash_postgres/compare/0.4.0...v0.5.0) (2020-06-29) - -### Features: - -- upgrade to latest ash - -## [v0.4.0](https://github.com/ash-project/ash_postgres/compare/0.3.0...v0.4.0) (2020-06-27) - -### Features: - -- update to latest ash - -## [v0.3.0](https://github.com/ash-project/ash_postgres/compare/0.2.1...v0.3.0) (2020-06-19) - -### Features: - -- New filter style (#10) - -## [v0.2.1](https://github.com/ash-project/ash_postgres/compare/0.2.0...v0.2.1) (2020-06-15) - -### Bug Fixes: - -- update .formatter.exs - -## [v0.2.0](https://github.com/ash-project/ash_postgres/compare/0.1.4...v0.2.0) (2020-06-14) - -### Features: - -- use the new DSL builder for config (#7) - -## [v0.1.4](https://github.com/ash-project/ash_postgres/compare/0.1.3...v0.1.4) (2020-06-05) - -### Bug Fixes: - -- update ash version dependency - -- account for removal of name - -## [v0.1.3](https://github.com/ash-project/ash_postgres/compare/0.1.2...v0.1.3) (2020-06-03) - -This release was a test of our automatic hex.pm package deployment - -## Begin Changelog +- [AshPostgres.Repo] warn on missing ash-functions at compile time +- [AshPostgres.Repo] add default implementation for pg_version, and rename to `min_pg_version` +- [mix ash.rollback] support `mix ash.rollback` with interactive rollback +- [AshSql] move many internals out to `AshSql` package to be shared diff --git a/documentation/1.0-CHANGELOG.md b/documentation/1.0-CHANGELOG.md new file mode 100644 index 00000000..222e3265 --- /dev/null +++ b/documentation/1.0-CHANGELOG.md @@ -0,0 +1,3903 @@ +## [v2.0.0-rc.14](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-04-29) + +### Improvements: + +- support latest ash & calculate/3 capability + +## [v2.0.0-rc.13](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.12...v2.0.0-rc.13) (2024-04-27) + +### Bug Fixes: + +- ensure limit/offset triggers joining for update/destroy query + +- only reference `sub` if a subquery is created + +## [v2.0.0-rc.12](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2024-04-27) + +### Bug Fixes: + +- update ash_sql for inner join fixes + +- fix argument order in AshSql.Bindings.default_bindings/4 (#251) + +## [v2.0.0-rc.11](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2024-04-24) + +### Bug Fixes: + +- properly honor `limit` in bulk operations + +## [v2.0.0-rc.10](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2024-04-23) + +### Bug Fixes: + +- undo change that expresses that atomics cant be done without `ash-functions` + +## [v2.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.8...v2.0.0-rc.9) (2024-04-23) + +### Breaking Changes: + +- change defaults for uuids to `gen_random_uuid()` + +- Use UTC for default generated timestamps (#131) + +- 3.0 (#227) + +### Features: + +- add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Bug Fixes: + +- handle missing aggregate relationships and fields better in transformers + +- update ash_sql for bug fixes + +- reproduce issue around atomic updates & validations + +- ensure that `exists` with a filter paired with `from_many?` functions properly + +- update ash_sql, fix credo + +- use proper sql implementation in `default_bindings` + +- don't wait for shell input when checking migrations + +- properly handle non-filter aggregate filters + +- ensure timestamps are present in extension migrations + +- handle fully fleshed out aggregate fields + +### Improvements: + +- warn on missing ash-functions at compile time + +- support `mix ash.rollback` with interactive rollback + +- don't fetch version in agent when using sandbox + +- loosen 3.0 release candidate requirement + +- fixes for 3.0 changes and AshSql changes + +- move many internals out to `AshSql` package + +- add default implementation for pg_version, and rename to `min_pg_version` + +- upgrade to 3.0 + +- properly show unsupported error expression + +## [v2.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2024-04-22) + +### Bug Fixes: + +- ensure that `exists` with a filter paired with `from_many?` functions properly + +## [v2.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2024-04-12) + +### Bug Fixes: + +- update ash_sql, fix credo + +## [v2.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.5...v2.0.0-rc.6) (2024-04-10) + +### Bug Fixes: + +- use proper sql implementation in `default_bindings` + +### Improvements: + +- support `mix ash.rollback` with interactive rollback + +## [v2.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.4...v2.0.0-rc.5) (2024-04-05) + +### Bug Fixes: + +- don't wait for shell input when checking migrations + +### Improvements: + +- don't fetch version in agent when using sandbox + +## [v2.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.3...v2.0.0-rc.4) (2024-04-02) + +### Improvements: + +- loosen 3.0 release candidate requirement + +## [v2.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2024-04-01) + +### Improvements: + +- fixes for 3.0 changes and AshSql changes + +- move many internals out to `AshSql` package + +## [v2.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.1...v2.0.0-rc.2) (2024-03-29) + +### Breaking Changes: + +- change defaults for uuids to `gen_random_uuid()` + +- Use UTC for default generated timestamps (#131) + +- 3.0 (#227) + +### Features: + +- add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Bug Fixes: + +- properly handle non-filter aggregate filters + +- ensure timestamps are present in extension migrations + +- handle fully fleshed out aggregate fields + +### Improvements: + +- add default implementation for pg_version, and rename to `min_pg_version` + +- upgrade to 3.0 + +- properly show unsupported error expression + +## [v2.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.0...v2.0.0-rc.1) (2024-03-28) + +### Improvements: + +- add default implementation for pg_version, and rename to `min_pg_version` + +## [v2.0.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.5.22...v2.0.0-rc.0) (2024-03-27) + +### Breaking Changes: + +- change defaults for uuids to `gen_random_uuid()` + +- Use UTC for default generated timestamps (#131) + +### Features: + +- add `create?` and `drop?` callbacks to `AshPostgres.Repo` (#143) + +### Improvements: + +- show proper error when using error expresison without `ash-functions` extension + +## [v1.5.22](https://github.com/ash-project/ash_postgres/compare/v1.5.21...v1.5.22) (2024-03-20) + +### Bug Fixes: + +- don't fail on aggregate query generation + +## [v1.5.21](https://github.com/ash-project/ash_postgres/compare/v1.5.20...v1.5.21) (2024-03-20) + +### Bug Fixes: + +- properly format migrations + +- ensure exists aggregates have filters included + +## [v1.5.20](https://github.com/ash-project/ash_postgres/compare/v1.5.19...v1.5.20) (2024-03-20) + +### Bug Fixes: + +- undo default of nulls_distinct option to true (#223) + +- generate correct custom index name in down migration function (#222) + +## [v1.5.19](https://github.com/ash-project/ash_postgres/compare/v1.5.18...v1.5.19) (2024-03-19) + +### Bug Fixes: + +- encode maps on update using fragments + +### Improvements: + +- Add nulls_distinct option to CustomIndex (#221) + +## [v1.5.18](https://github.com/ash-project/ash_postgres/compare/v1.5.17...v1.5.18) (2024-03-19) + +### Bug Fixes: + +- don't reuse binding in many to many aggregate joins + +- typo in extension generator creates invalid drop + +- merge base_filter and custom index's where correctly (#219) + +### Improvements: + +- properly format generated migrations + +- don't select fields in exists subquery + +## [v1.5.17](https://github.com/ash-project/ash_postgres/compare/v1.5.16...v1.5.17) (2024-03-06) + +### Bug Fixes: + +- prevent ecto/pg from getting confused about the type of maps + +## [v1.5.16](https://github.com/ash-project/ash_postgres/compare/v1.5.15...v1.5.16) (2024-03-05) + +### Bug Fixes: + +- always exclude `:order_by` on bulk updateable query + +- don't apply join relationship sort for lateral join + +## [v1.5.15](https://github.com/ash-project/ash_postgres/compare/v1.5.14...v1.5.15) (2024-03-01) + +### Improvements: + +- don't double cast to the same type + +- detect more types + +## [v1.5.14](https://github.com/ash-project/ash_postgres/compare/v1.5.13...v1.5.14) (2024-03-01) + +### Improvements: + +- no need for subquery for simple table aliases + +## [v1.5.13](https://github.com/ash-project/ash_postgres/compare/v1.5.12...v1.5.13) (2024-02-29) + +### Bug Fixes: + +- properly handle multiple sorts in aggregate + +## [v1.5.12](https://github.com/ash-project/ash_postgres/compare/v1.5.11...v1.5.12) (2024-02-29) + +### Bug Fixes: + +- ensure that `from_many?` joins are properly limited + +- ensure that lateral joins are properly filtered + +## [v1.5.11](https://github.com/ash-project/ash_postgres/compare/v1.5.10...v1.5.11) (2024-02-29) + +### Bug Fixes: + +- simplify(and fix) exists subquery generation + +- properly leverage subqueries throughout relationship joining + +- migration generator extensions in multiple repos (#214) + +- Migration generator for extensions in multiple repos + +### Improvements: + +- optimize more cases for simple join aggregates + +## [v1.5.10](https://github.com/ash-project/ash_postgres/compare/v1.5.9...v1.5.10) (2024-02-26) + +### Bug Fixes: + +- fix error when encoding vectors + +- ensure select is applied (or not) properly in bulk update/destroys + +## [v1.5.9](https://github.com/ash-project/ash_postgres/compare/v1.5.8...v1.5.9) (2024-02-25) + +### Bug Fixes: + +- handle more subquery filter cases for aggregates + +- only apply filters inside aggregate subquery + +### Improvements: + +- add test for aggregates + +## [v1.5.8](https://github.com/ash-project/ash_postgres/compare/v1.5.7...v1.5.8) (2024-02-24) + +### Bug Fixes: + +- properly handle complex types in lists + +## [v1.5.7](https://github.com/ash-project/ash_postgres/compare/v1.5.6...v1.5.7) (2024-02-22) + +### Bug Fixes: + +- properly apply lateral join conditions to left lateral joins + +## [v1.5.6](https://github.com/ash-project/ash_postgres/compare/v1.5.5...v1.5.6) (2024-02-21) + +### Bug Fixes: + +- ensure select is properly set on delete_all + +### Improvements: + +- optimize aggregate query filtering + +## [v1.5.5](https://github.com/ash-project/ash_postgres/compare/v1.5.4...v1.5.5) (2024-02-21) + +### Bug Fixes: + +- ensure proper return value for single aggregate runs + +## [v1.5.4](https://github.com/ash-project/ash_postgres/compare/v1.5.3...v1.5.4) (2024-02-21) + +### Bug Fixes: + +- don't sort a query that will be used with `delete_all` + +- ensure that `exists?` aggregates use `repo.exists?` + +- properly handle to_many joins in aggregates + +- honor aggregate query filters + +- use proper tables in joins originating from polymorphic resource (#211) + +- properly transfer table names to non-inner wrapper queries (#210) + +## [v1.5.3](https://github.com/ash-project/ash_postgres/compare/v1.5.2...v1.5.3) (2024-02-19) + +### Bug Fixes: + +- handle non-inner joins in delete_all + +- handle non-inner joins in update + +## [v1.5.2](https://github.com/ash-project/ash_postgres/compare/v1.5.1...v1.5.2) (2024-02-19) + +### Bug Fixes: + +- don't update_all or delete_all with `order_by` + +- handle updating from queries w/ non-inner initial joins + +## [v1.5.1](https://github.com/ash-project/ash_postgres/compare/v1.5.0...v1.5.1) (2024-02-19) + +### Bug Fixes: + +- joining to `from_many?: true` relationships not honoring limit + +## [v1.5.0](https://github.com/ash-project/ash_postgres/compare/v1.4.0...v1.5.0) (2024-02-16) + +### Features: + +- Make MigrationGenerator accept atoms (#201) + +### Bug Fixes: + +- allow subquerying a `through` while aggregating a many to many + +- don't subquery if we need to reference `parent_as` + +- avoid double wrapping in subqueries + +- properly set 0 binding on joined subquery creation + +- properly alter renaming attributes in migration generator + +- handle original data not available in destroy_query + +- use primary key of source as join key + +- use pkey if error fields is empty + +- forgot to bind keys to a variable 🤦🏻 + +- ensure identity keys is never missing + +- properly build subqueries when required for relationship queries + +- only migrate/rollback one repo at a time + +- proper return types for updates from queries + +- allow atomics to return `nil` + +- Correct the matching used in building a distinct expression (#196) + +- only rollback to savepoint on specific errors + +- keep fields of `custom_index` in format that they were provided (#195) + +- remap selected fields, don't subquery in aggregate joins + +- include explicit schema in snapshot folder name + +- Support all_tenants? in custom index (#194) + +### Improvements: + +- update to latest ash + +- mark (i)like functions as predicates (#205) + +- detect bigserial when altering attributes + +- Include modules in installed_extensions return type (#202) + +- don't drop primary key in case of removal + +- handle if select is present on query + +- support `Ash.Changeset.OriginalDataNotAvailable` + +- support `count_nils` expression + +- `error_fields` for `custom_index` + +- support latest ash changes + +## [v1.4.0](https://github.com/ash-project/ash_postgres/compare/v1.3.68...v1.4.0) (2024-01-12) + +### Features: + +- Add unit test to check lateral joins + +### Bug Fixes: + +- unset sort/distinct on related queries + +- subquery relationships that have filters + +- don't overwrite manually set schema on lateral join query + +- properly configure `polymorphic_name` option + +- honor configured schema on bulk create + +### Improvements: + +- support `all_tenants?` option for identities + +- support `all_tenants?` option for custom indexes + +- support join_filters on aggregates + +- use the target action when generating related queries + +## [v1.3.68](https://github.com/ash-project/ash_postgres/compare/v1.3.67...v1.3.68) (2024-01-04) + +### Bug Fixes: + +- properly gather types for operator & function overloads + +## [v1.3.67](https://github.com/ash-project/ash_postgres/compare/v1.3.66...v1.3.67) (2024-01-04) + +### Bug Fixes: + +- support encoding errors with expressions in them + +### Improvements: + +- support latest ash version & operator overrides + +- support new bulk operations + +## [v1.3.66](https://github.com/ash-project/ash_postgres/compare/v1.3.65...v1.3.66) (2023-12-30) + +### Improvements: + +- support new `return_query/2` callback + +- support new `:no_rollback` error signal + +- require `name` when generating migrations + +- support directly referencing aggregates from aggregates + +- support aggregates as `get_path` subject + +## [v1.3.65](https://github.com/ash-project/ash_postgres/compare/v1.3.64...v1.3.65) (2023-12-23) + +### Bug Fixes: + +- various fixes for unnecessary aggregate additions + +- use lateral joins when joining to subquery w/ parent reference + +- replace upsert field with source in EXCLUDED fragment (#187) + +- handle strings in get_path + +- reenable mix tasks that need calling + +### Improvements: + +- support aggregates using other aggregates + +- support string_length and string_trim + +- only start savepoints when necessary + +- clean up nested if statements to single case statements + +- support for `error/2` expression + +## [v1.3.64](https://github.com/ash-project/ash_postgres/compare/v1.3.63...v1.3.64) (2023-12-04) + +### Bug Fixes: + +- properly cast lazy update defaults to target type + +## [v1.3.63](https://github.com/ash-project/ash_postgres/compare/v1.3.62...v1.3.63) (2023-12-03) + +### Bug Fixes: + +- use maps for composite_type instead of tuples + +- avoid empty error on upserts with `:nothing` + +- simplify aggregate bindings & calculation reference building + +- hydrate aggregate refs when adding for calculations + +- apply limit to `from_many?` relationship joins + +- properly add filters for exists aggregates + +- properly expand calculation values across aggregate invocations + +- don't add filter for `no_attributes?` relationships + +- handle `no_attributes?` flag on aggregates better + +- properly handle sorted relationships in aggregates + +### Improvements: + +- support `composite_type/2` expression + +- support composite types + +- optimize relationships with identity on other end + +- allow specifying multi-column foreign keys (#180) + +- add match_with option on references + +- add match_type option on references + +## [v1.3.62](https://github.com/ash-project/ash_postgres/compare/v1.3.61...v1.3.62) (2023-11-16) + +### Bug Fixes: + +- use `synonymous_relationship_path` when looking up ref bindings + +- add calculation context to calculation expressions + +## [v1.3.61](https://github.com/ash-project/ash_postgres/compare/v1.3.60...v1.3.61) (2023-11-15) + +### Bug Fixes: + +- don't append update_defaults automatically if `upsert_fields` was set + +- don't ensure repo compiled at compile time + +- handle additional case for new functional repo callback + +- get resource from proper bindings on `exists` query + +### Improvements: + +- support a 2 argument function for the repo option + +- spport `CURRENT_DATE` default + +## [v1.3.60](https://github.com/ash-project/ash_postgres/compare/v1.3.59...v1.3.60) (2023-10-27) + +### Improvements: + +- support `parent` in sort expressions + +## [v1.3.59](https://github.com/ash-project/ash_postgres/compare/v1.3.58...v1.3.59) (2023-10-25) + +### Improvements: + +- join relationships for aggregate filters + +## [v1.3.58](https://github.com/ash-project/ash_postgres/compare/v1.3.57...v1.3.58) (2023-10-24) + +### Bug Fixes: + +- don't traverse new types for storage type + +- properly join to related references in relationship filters + +## [v1.3.57](https://github.com/ash-project/ash_postgres/compare/v1.3.56...v1.3.57) (2023-10-17) + +### Improvements: + +- allow for combining `AshPostgres.Repo` with other repos + +## [v1.3.56](https://github.com/ash-project/ash_postgres/compare/v1.3.55...v1.3.56) (2023-10-11) + +### Bug Fixes: + +- don't raise all errors + +## [v1.3.55](https://github.com/ash-project/ash_postgres/compare/v1.3.54...v1.3.55) (2023-10-11) + +### Improvements: + +- support atomics on upserts + +## [v1.3.54](https://github.com/ash-project/ash_postgres/compare/v1.3.53...v1.3.54) (2023-10-10) + +### Bug Fixes: + +- fix type specification for foreign_key_names + +## [v1.3.53](https://github.com/ash-project/ash_postgres/compare/v1.3.52...v1.3.53) (2023-10-10) + +### Bug Fixes: + +- don't run main query if only `exists` aggs are specified + +- subquery aggregate if limit is applied + +### Improvements: + +- update ash dependency + +- support `:ci_string` as a storage_type + +- support to-one references in calculations + +## [v1.3.52](https://github.com/ash-project/ash_postgres/compare/v1.3.51...v1.3.52) (2023-09-26) + +### Bug Fixes: + +- use `:wrap_list` type instead of custom validaitons (#167) + +### Improvements: + +- fix `upsert_fields` behavior for upserts + +- support data_layer_context option on transactions + +## [v1.3.51](https://github.com/ash-project/ash_postgres/compare/v1.3.50...v1.3.51) (2023-09-20) + +### Improvements: + +- add `AshPostgres.Tsvector` + +- add AshPostgres.Tsquery + +- support vector types and `vector_cosine_distance` + +## [v1.3.50](https://github.com/ash-project/ash_postgres/compare/v1.3.49...v1.3.50) (2023-09-06) + +### Improvements: + +- Allow resources to opt out of the primary key requirement. (#166) + +## [v1.3.49](https://github.com/ash-project/ash_postgres/compare/v1.3.48...v1.3.49) (2023-09-04) + +### Improvements: + +- implement ash lifecycle tasks + +## [v1.3.48](https://github.com/ash-project/ash_postgres/compare/v1.3.47...v1.3.48) (2023-09-04) + +### Improvements: + +- better error message for missing table config + +## [v1.3.47](https://github.com/ash-project/ash_postgres/compare/v1.3.46...v1.3.47) (2023-08-31) + +### Bug Fixes: + +- ensure we always select at least one field, and change one field + +## [v1.3.46](https://github.com/ash-project/ash_postgres/compare/v1.3.45...v1.3.46) (2023-08-31) + +### Bug Fixes: + +- use provided values for updates + +## [v1.3.45](https://github.com/ash-project/ash_postgres/compare/v1.3.44...v1.3.45) (2023-08-31) + +### Bug Fixes: + +- don't clobber loaded data on update + +## [v1.3.44](https://github.com/ash-project/ash_postgres/compare/v1.3.43...v1.3.44) (2023-08-31) + +### Bug Fixes: + +- properly handle ensure nsted calls to `get_path` are jsonb + +### Improvements: + +- support atomics (#165) + +## [v1.3.43](https://github.com/ash-project/ash_postgres/compare/v1.3.42...v1.3.43) (2023-08-22) + +### Bug Fixes: + +- properly provide constraints on all type casting + +## [v1.3.42](https://github.com/ash-project/ash_postgres/compare/v1.3.41...v1.3.42) (2023-08-22) + +### Bug Fixes: + +- support non-atom named aggregates + +- handle case where multiple grouped aggregates depend on further aggregates + +### Improvements: + +- support in-line aggregates + +- specify @behaviour in AshPostgres.Type + +- add `value_to_postgres_default/3` and `AshPostgres.Type` + +- handle non-cast-in-type queries + +## [v1.3.41](https://github.com/ash-project/ash_postgres/compare/v1.3.40...v1.3.41) (2023-08-08) + +### Bug Fixes: + +- handle interaction between distinct, join filters and sort + +### Improvements: + +- custom-extension implementation (#162) + +- custom-extension implementation + +- allow adding custom-extension by module's reference and fixes formatting + +- support new `from_many?` option + +- subquery after distinct to handle distinct + +## [v1.3.40](https://github.com/ash-project/ash_postgres/compare/v1.3.39...v1.3.40) (2023-08-01) + +### Bug Fixes: + +- properly detect optimizable first aggregates + +## [v1.3.39](https://github.com/ash-project/ash_postgres/compare/v1.3.38...v1.3.39) (2023-08-01) + +### Bug Fixes: + +- properly alter deferrability on attribute alter + +### Improvements: + +- update ash + +- handle empty maps in migration defaults automatically + +- handle empty lists in migraiton defaults automatically + +- apply sort in subqueries properly + +- handle `no_attributes?` better in more places + +- support the new `parent/1` expr in relationships + +- explicitly lock the source row + +## [v1.3.38](https://github.com/ash-project/ash_postgres/compare/v1.3.37...v1.3.38) (2023-07-21) + +### Bug Fixes: + +- un-break aggregates referencing calculations + +### Improvements: + +- properly handle context for referenced calculations + +## [v1.3.37](https://github.com/ash-project/ash_postgres/compare/v1.3.36...v1.3.37) (2023-07-19) + +### Improvements: + +- support new `distinct_sort` option + +## [v1.3.36](https://github.com/ash-project/ash_postgres/compare/v1.3.35...v1.3.36) (2023-07-19) + +### Bug Fixes: + +- type casting improvements, handle manual relationships in `exists` + +- protected names in conflict_target (#158) + +## [v1.3.35](https://github.com/ash-project/ash_postgres/compare/v1.3.34...v1.3.35) (2023-07-18) + +### Improvements: + +- support new `distinct` features from ash core + +## [v1.3.34](https://github.com/ash-project/ash_postgres/compare/v1.3.33...v1.3.34) (2023-07-18) + +### Improvements: + +- support unary `-/1` operator + +## [v1.3.33](https://github.com/ash-project/ash_postgres/compare/v1.3.32...v1.3.33) (2023-07-14) + +### Bug Fixes: + +- convert `Ash.Resource.Aggregate` to `Ash.Query.Aggregate` when adding + +### Improvements: + +- support `deferrable` option in migration generator + +- support `exists` aggregates + +## [v1.3.32](https://github.com/ash-project/ash_postgres/compare/v1.3.31...v1.3.32) (2023-07-12) + +### Improvements: + +- support `at/2` expression + +## [v1.3.31](https://github.com/ash-project/ash_postgres/compare/v1.3.30...v1.3.31) (2023-07-12) + +### Bug Fixes: + +- raise better error on invalid filter values + +- Fixes multiple schema identities migrations (#156) + +- fix Logger deprecations for elixir 1.15 (#155) + +- interpolate table names with `inspect` in generated migrations (#152) + +### Improvements: + +- better `ash_functions` message + +- support `string_split` + +- add postgres expressions guide + +- add `simple_join_first_aggregates` option + +## [v1.3.30](https://github.com/ash-project/ash_postgres/compare/v1.3.29...v1.3.30) (2023-06-06) + +### Bug Fixes: + +- handle changing custom index names better + +- validate custom index names + +## [v1.3.29](https://github.com/ash-project/ash_postgres/compare/v1.3.28...v1.3.29) (2023-06-05) + +### Bug Fixes: + +- properly handle nested aggregate references + +## [v1.3.28](https://github.com/ash-project/ash_postgres/compare/v1.3.27...v1.3.28) (2023-05-23) + +### Bug Fixes: + +- handle raised errors in bulk actions + +## [v1.3.27](https://github.com/ash-project/ash_postgres/compare/v1.3.26...v1.3.27) (2023-05-17) + +### Improvements: + +- raise better errors on conflicting locks + +## [v1.3.26](https://github.com/ash-project/ash_postgres/compare/v1.3.25...v1.3.26) (2023-05-16) + +### Bug Fixes: + +- use proper lock list again + +- use proper list of row level locks + +- check `changeset.action_type` not `changeset.action.type` + +### Improvements: + +- support more lock types + +## [v1.3.25](https://github.com/ash-project/ash_postgres/compare/v1.3.24...v1.3.25) (2023-05-08) + +### Improvements: + +- support changeset.filters (for optimistic locking) + +## [v1.3.24](https://github.com/ash-project/ash_postgres/compare/v1.3.23...v1.3.24) (2023-05-03) + +### Improvements: + +- support bulk upserts + +## [v1.3.23](https://github.com/ash-project/ash_postgres/compare/v1.3.22...v1.3.23) (2023-05-01) + +### Bug Fixes: + +- don't incorrectly mark references as primary key references + +- go back to old migration sorting algorithm + +## [v1.3.22](https://github.com/ash-project/ash_postgres/compare/v1.3.21...v1.3.22) (2023-04-28) + +### Improvements: + +- support locking + +## [v1.3.21](https://github.com/ash-project/ash_postgres/compare/v1.3.20...v1.3.21) (2023-04-27) + +### Improvements: + +- handle new spark versions better, more explicit snapshots + +## [v1.3.20](https://github.com/ash-project/ash_postgres/compare/v1.3.19...v1.3.20) (2023-04-22) + +### Bug Fixes: + +- subquery aggregates when a distinct is being added + +- don't call `.table` on `nil` + +- wrap `datetime_add` in parenthesis + +- handle primary key changes properly + +### Improvements: + +- update ash + +- don't call `.table` on `nil` `snapshot` + +- use digraph for operation ordering + +## [v1.3.19](https://github.com/ash-project/ash_postgres/compare/v1.3.18...v1.3.19) (2023-04-07) + +### Bug Fixes: + +- properly handle newtypes, add test + +- honor newtypes when determining migration type + +- handle nil ash_functions_version in another place + +- handle nil ash_functions_version + +### Improvements: + +- update ash + +## [v1.3.18](https://github.com/ash-project/ash_postgres/compare/v1.3.17...v1.3.18) (2023-03-23) + +## [v1.3.17](https://github.com/ash-project/ash_postgres/compare/v1.3.16...v1.3.17) (2023-03-20) + +### Bug Fixes: + +- properly map `parent` bindings in `exists` + +## [v1.3.16](https://github.com/ash-project/ash_postgres/compare/v1.3.15...v1.3.16) (2023-03-03) + +### Improvements: + +- support new date expressions + +## [v1.3.15](https://github.com/ash-project/ash_postgres/compare/v1.3.14...v1.3.15) (2023-02-23) + +### Improvements: + +- add aggregates used by sorts + +## [v1.3.14](https://github.com/ash-project/ash_postgres/compare/v1.3.13...v1.3.14) (2023-02-21) + +### Improvements: + +- Implement string_join expr (#132) + +## [v1.3.13](https://github.com/ash-project/ash_postgres/compare/v1.3.12...v1.3.13) (2023-02-17) + +### Bug Fixes: + +- don't use `:distinct` when `uniq?` is not `true` + +## [v1.3.12](https://github.com/ash-project/ash_postgres/compare/v1.3.11...v1.3.12) (2023-02-16) + +### Bug Fixes: + +- exclude `order_by` when building aggregates + +## [v1.3.11](https://github.com/ash-project/ash_postgres/compare/v1.3.10...v1.3.11) (2023-02-16) + +### Bug Fixes: + +- properly find migration directories in umbrella apps + +- don't double-cast to array for list aggregates + +### Improvements: + +- significantly optimize aggregate queries + +- better type casting for concat operator + +## [v1.3.10](https://github.com/ash-project/ash_postgres/compare/v1.3.9...v1.3.10) (2023-02-09) + +### Bug Fixes: + +- sorting on optimized first aggregates + +## [v1.3.9](https://github.com/ash-project/ash_postgres/compare/v1.3.8...v1.3.9) (2023-02-09) + +### Bug Fixes: + +- do limit/offset outside of query if distinct is required + +- load by **order** ascending + +### Improvements: + +- support new `uniq?` option on count/list aggregates + +- optimized `first` aggregates where possible + +## [v1.3.8](https://github.com/ash-project/ash_postgres/compare/v1.3.7...v1.3.8) (2023-02-06) + +### Bug Fixes: + +- Actually use `AshPostgres.Repo` behaviour (#129) + +### Improvements: + +- authorization filters are now attached by ash core + +## [v1.3.7](https://github.com/ash-project/ash_postgres/compare/v1.3.6...v1.3.7) (2023-02-06) + +### Bug Fixes: + +- Actually use `AshPostgres.Repo` behaviour (#129) + +### Improvements: + +- authorization filters are now attached by ash core + +## [v1.3.6](https://github.com/ash-project/ash_postgres/compare/v1.3.5...v1.3.6) (2023-02-03) + +### Bug Fixes: + +- properly set next migration name + +- override `insert` function for proper ecto interop + +### Improvements: + +- add `migration_ignore_attributes` + +## [v1.3.5](https://github.com/ash-project/ash_postgres/compare/v1.3.4...v1.3.5) (2023-01-29) + +### Bug Fixes: + +- properly convert to/from ecto, only when necessary + +## [v1.3.4](https://github.com/ash-project/ash_postgres/compare/v1.3.3...v1.3.4) (2023-01-28) + +### Bug Fixes: + +- support latest ecto interop changes in ash core + +### Improvements: + +- properly cast division to floats for elixir-y behavior + +- support for dynamically set repo + +- update ash + +## [v1.3.3](https://github.com/ash-project/ash_postgres/compare/v1.3.2...v1.3.3) (2023-01-18) + +### Improvements: + +- update to new docs patterns + +## [v1.3.2](https://github.com/ash-project/ash_postgres/compare/v1.3.1...v1.3.2) (2023-01-17) + +### Bug Fixes: + +- nest subqueries when required for distinct + +- replace `{:in, ...}` type with `{:array, ...}` + +## [v1.3.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0...v1.3.1) (2023-01-11) + +### Bug Fixes: + +- allow for non attribute aggregate references for first/list + +## [v1.3.0](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.4...v1.3.0) (2023-01-11) + +### Improvements: + +- update to latest ash + +## [v1.3.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.3...v1.3.0-rc.4) (2023-01-09) + +### Bug Fixes: + +- properly join to all required relationships + +## [v1.3.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.2...v1.3.0-rc.3) (2023-01-09) + +### Bug Fixes: + +- properly type cast in fragments (and elsewhere) + +## [v1.3.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.1...v1.3.0-rc.2) (2023-01-06) + +### Bug Fixes: + +- undo changes that caused type casting bugs + +## [v1.3.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.0...v1.3.0-rc.1) (2023-01-06) + +### Bug Fixes: + +- undo changes that caused type casting bugs + +## [v1.3.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.3.0-rc.0...v1.3.0-rc.1) (2023-01-06) + +### Bug Fixes: + +- use `parent_expr` instead of `this` + +- various expression & type building fixes + +## [v1.3.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.2.6...v1.3.0-rc.0) (2023-01-04) + +### Features: + +- support latest ash + +### Bug Fixes: + +- honor calculation constraints + +- handle lists with expressions inside + +### Improvements: + +- support calc constraints + +- support new `cast_in_query?/2` + +- support calculations as aggregate targets + +## [v1.2.6](https://github.com/ash-project/ash_postgres/compare/v1.2.5...v1.2.6) (2022-12-27) + +### Bug Fixes: + +- properly set `migrations_path` default in umbrellas + +- don't subquery unless we have to + +## [v1.2.5](https://github.com/ash-project/ash_postgres/compare/v1.2.4...v1.2.5) (2022-12-21) + +### Bug Fixes: + +- don't group aggregates that reference relationships in their filters + +- properly skip unique indexes when configured + +### Improvements: + +- add like and ilike + +## [v1.2.4](https://github.com/ash-project/ash_postgres/compare/v1.2.3...v1.2.4) (2022-12-18) + +### Bug Fixes: + +- properly add aggregates to query when referenced from calculations + +### Improvements: + +- distinct on source of query, not relationship destination + +## [v1.2.3](https://github.com/ash-project/ash_postgres/compare/v1.2.2...v1.2.3) (2022-12-15) + +### Bug Fixes: + +- properly combine sort + to many join filter + +## [v1.2.2](https://github.com/ash-project/ash_postgres/compare/v1.2.1...v1.2.2) (2022-12-15) + +### Improvements: + +- udpate to latest ash, fix array issues + +## [v1.2.1](https://github.com/ash-project/ash_postgres/compare/v1.2.0...v1.2.1) (2022-12-13) + +### Bug Fixes: + +- pattern match error in `lazy_non_matching_defaults/1` + +- use attribute name not attribute for default funs + +- _actually_ fix `default_fun` upserts + +- fix upserting update_defaults + +## [v1.2.0](https://github.com/ash-project/ash_postgres/compare/v1.2.0-rc.1...v1.2.0) (2022-12-13) + +### Bug Fixes: + +- make migration generator work better for umbrellas + +## [v1.2.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.2.0-rc.0...v1.2.0-rc.1) (2022-12-10) + +### Bug Fixes: + +- don't make migration generation recursive + +- nevermind, can't make migrate recursive + +### Improvements: + +- make migrate task recursive as well + +- mark generate_migrations as recursive for umbrellas + +## [v1.2.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v1.1.3...v1.2.0-rc.0) (2022-12-10) + +### Features: + +- avg/min/max/custom aggregate support + +### Bug Fixes: + +- various broken behavior from new aggregate work + +- forgot a + +- fix various problems with the model behind aggregates + +- properly set binding names for many to many join filters + +### Improvements: + +- better error messages from mix tasks + +- validate that references refer to relationships + +- avg/min/max/custom aggregate support + +- upgrade and depend on ash version + +- fix lateral many to many joins + +- inform users about postgres incompatibility with multidimensional arrays + +## [v1.1.3](https://github.com/ash-project/ash_postgres/compare/v1.1.2...v1.1.3) (2022-12-01) + +### Bug Fixes: + +- properly turn custom index keys into atoms + +### Improvements: + +- update ash, add test for transaction hooks + +- support new transaction info with hooks + +- add unique constraints to changeset for custom unique indexes + +- separate out concurrent index creations and do them in a separate transaction + +## [v1.1.2](https://github.com/ash-project/ash_postgres/compare/v1.1.1...v1.1.2) (2022-11-21) + +### Bug Fixes: + +- don't use hard-coded join assoc name (#118) + +### Improvements: + +- add `migration_defaults` for customizing default values + +## [v1.1.1](https://github.com/ash-project/ash_postgres/compare/v1.1.0...v1.1.1) (2022-10-25) + +### Bug Fixes: + +- && operator in expressions to point to ash_elixir_and (#115) + +### Improvements: + +- add check for unsupported expression + +## [v1.1.0](https://github.com/ash-project/ash_postgres/compare/v1.0.0...v1.1.0) (2022-10-20) + +### Features: + +- support `now()` in latest Ash + +## [v1.0.0](https://github.com/ash-project/ash_postgres/compare/v0.43.0...v1.0.0) (2022-10-17) + +### Bug Fixes: + +- no unnecessary type cast on count/sum aggregates + +- don't apply `filter` to `array_agg` + +### Improvements: + +- update to Ash 2.0 + +- handle UUID types better + +- set lateral join source for latest ash + +- use `prepend?: true` option when applying relationship sorts + +## [v1.0.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.8...v1.0.0-rc.9) (2022-10-07) + +### Bug Fixes: + +- handle custom calculation selects properly + +- use attribute source for identity fields + +### Improvements: + +- update to the latest ash + +- remove the need to dynamically expand fragments + +- when casting string to uuid, dump to binary + +- update to latest ash + +## [v1.0.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.7...v1.0.0-rc.8) (2022-09-29) + +### Bug Fixes: + +- never attempt to group custom operations + +- wrap case statement in parens + +### Improvements: + +- `exists` filters necessitate multiple aggregate joins (for now) + +## [v1.0.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.6...v1.0.0-rc.7) (2022-09-28) + +### Bug Fixes: + +- properly type cast top level fragments + +### Improvements: + +- update to the latest ash + +- upgrade to new `exists` usage + +## [v1.0.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.5...v1.0.0-rc.6) (2022-09-21) + +### Improvements: + +- support latest ash + +## [v1.0.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.4...v1.0.0-rc.5) (2022-09-15) + +### Improvements: + +- update to latest ash + +- implement Length function (#111) + +- upgrade to latest ash + +## [v1.0.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.3...v1.0.0-rc.4) (2022-09-14) + +### Improvements: + +- support latest ash + +- support manual relationships with joins + +## [v1.0.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.2...v1.0.0-rc.3) (2022-09-12) + +### Bug Fixes: + +- keep unique index keys in order in migrations + +## [v1.0.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.1...v1.0.0-rc.2) (2022-09-06) + +### Improvements: + +- support latest ash `exists/2` expr + +## [v1.0.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v1.0.0-rc.0...v1.0.0-rc.1) (2022-09-04) + +## [v0.43.0](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.7...v0.43.0) (2022-08-05) + +### Bug Fixes: + +- properly order check constraints + +- remove check constraints before adding them + +### Improvements: + +- fix typecasting for calculations & embed access + +- add custom_statements to migration generator + +- support `||` and `&&` + +## [v0.42.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.6...v0.42.0-rc.7) (2022-07-14) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- use new doc_index patterns + +- support upsert_identity with base_filter + +- support upsert_identity with base filters + +- handle various join bugs + +- use attribute.name if attribute.source is nil + +- set attribute source properly + +- ensure source is always set on attributes in snapshots + +- handle paths for aggregates w/ > 2 relationships + +- rename attributes correctly in down migration (#98) + +- don't generate modify commands for attributes due to schema changes + +- default schema to primary schema + +- test and confirm behavior of schemas + +- use correct bindings for filtered relationships + +- cast calcs in query expressions + +- explicitly type cast aggregate/calc selects + +- don't try and match reference schema to table schema + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- add default guide, and empty ash postgres guide + +- set `update_defaults` on upsert results + +- handle fallback ecto migration default elegantly (#94) + +- add `ignore?` option to `references` + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.5...v0.42.0-rc.6) (2022-07-10) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- use new doc_index patterns + +- support upsert_identity with base_filter + +- support upsert_identity with base filters + +- handle various join bugs + +- use attribute.name if attribute.source is nil + +- set attribute source properly + +- ensure source is always set on attributes in snapshots + +- handle paths for aggregates w/ > 2 relationships + +- rename attributes correctly in down migration (#98) + +- don't generate modify commands for attributes due to schema changes + +- default schema to primary schema + +- test and confirm behavior of schemas + +- use correct bindings for filtered relationships + +- cast calcs in query expressions + +- explicitly type cast aggregate/calc selects + +- don't try and match reference schema to table schema + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- set `update_defaults` on upsert results + +- handle fallback ecto migration default elegantly (#94) + +- add `ignore?` option to `references` + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.4...v0.42.0-rc.5) (2022-07-06) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- support upsert_identity with base_filter + +- support upsert_identity with base filters + +- handle various join bugs + +- use attribute.name if attribute.source is nil + +- set attribute source properly + +- ensure source is always set on attributes in snapshots + +- handle paths for aggregates w/ > 2 relationships + +- rename attributes correctly in down migration (#98) + +- don't generate modify commands for attributes due to schema changes + +- default schema to primary schema + +- test and confirm behavior of schemas + +- use correct bindings for filtered relationships + +- cast calcs in query expressions + +- explicitly type cast aggregate/calc selects + +- don't try and match reference schema to table schema + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- set `update_defaults` on upsert results. For most users, this means that where previously `updated_at` would not get set on an upsert that ultimately resulted in an update, it will now. + +- handle fallback ecto migration default elegantly (#94) + +- add `ignore?` option to `references` + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.3...v0.42.0-rc.4) (2022-06-28) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- use attribute.name if attribute.source is nil + +- set attribute source properly + +- ensure source is always set on attributes in snapshots + +- handle paths for aggregates w/ > 2 relationships + +- rename attributes correctly in down migration (#98) + +- don't generate modify commands for attributes due to schema changes + +- default schema to primary schema + +- test and confirm behavior of schemas + +- use correct bindings for filtered relationships + +- cast calcs in query expressions + +- explicitly type cast aggregate/calc selects + +- don't try and match reference schema to table schema + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- handle fallback ecto migration default elegantly (#94) + +- add `ignore?` option to `references` + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.2...v0.42.0-rc.3) (2022-06-28) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- set attribute source properly + +- ensure source is always set on attributes in snapshots + +- handle paths for aggregates w/ > 2 relationships + +- rename attributes correctly in down migration (#98) + +- don't generate modify commands for attributes due to schema changes + +- default schema to primary schema + +- test and confirm behavior of schemas + +- use correct bindings for filtered relationships + +- cast calcs in query expressions + +- explicitly type cast aggregate/calc selects + +- don't try and match reference schema to table schema + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- handle fallback ecto migration default elegantly (#94) + +- add `ignore?` option to `references` + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.1...v0.42.0-rc.2) (2022-05-18) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- don't try and match reference schema to table schema + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v0.42.0-rc.0...v0.42.0-rc.1) (2022-05-18) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- don't use `table` where we should use `schema` in migration generator + +- handle combinations of distinct & sort + +- ensure all single actions are explicitly marked as primary? (#95) + +- only rename schema when necessary + +- inspect un-defaultable value in error message + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- check_migrations, rename to `--check` + +- add explicit timeout capability declaration + +- add static schema specification in DSL + +- support static schema specification in migration generator + +- implement decimal ecto migration default (#91) + +- support float as Ecto migration default (#89) + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.42.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.41.7...v0.42.0-rc.0) (2022-04-26) + +### Features: + +- support `cast_in_query?/0` and `source` + +### Bug Fixes: + +- select custom aggregates properly + +- don't add reference when renaming column if unnecessary + +- don't cast `nil` to `""` + +- `!is_atom/1` -> `!is_boolean/1` + +- sanitize lists to stringify atoms + +- cast embedded atoms to strings first + +- don't cast `{:in, :any}` types + +- more don't cast any types + +- don't cast if there is no type + +- properly handle relationship filter bindings + +- don't consider fields changed with only source -> name changes + +- handle name -> source change in more places + +- handle name -> source rename in operation ordering + +- fix aggregate/base filters + +- don't select more fields than necessary + +- don't call `ecto_type` twice when resolving types + +- place expressions in the proper order in selects + +- match on count in expr + +- remove incorrect param count tracking + +- properly track param count + +- properly reverse parameters before/after expansion + +- don't use the base ecto type + +- don't sort when joining + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- update ecto + +- add atom impl for `EctoMigrationDefault` + +- Add EctoMigrationDefault protocol and implement defaults (#87) + +- update ecto, fix dialyzer + +- support new timeouts + +- make select unique before running query + +- add doc_index + +- add exclusion_constraint_names (#83) + +- support referencing aggregates from aggregate filters + +- support access syntax + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.7](https://github.com/ash-project/ash_postgres/compare/v0.41.6...v0.41.7) (2021-12-21) + +### Bug Fixes: + +- ensure repo is compiled (#80) + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.6](https://github.com/ash-project/ash_postgres/compare/v0.41.5...v0.41.6) (2021-12-21) + +### Bug Fixes: + +- properly construct nested join relationships + +- use `CiStringWrapper` type in ash_postgres + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.5](https://github.com/ash-project/ash_postgres/compare/v0.41.4...v0.41.5) (2021-11-26) + +### Bug Fixes: + +- ensure we are returning \* on upserts (#79) + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.4](https://github.com/ash-project/ash_postgres/compare/v0.41.3...v0.41.4) (2021-11-25) + +### Bug Fixes: + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- don't upsert defaults on conflict (#77) + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.3](https://github.com/ash-project/ash_postgres/compare/v0.41.2...v0.41.3) (2021-11-13) + +### Bug Fixes: + +- handle new if types + +- copy query prefix to newly created query (#74) + +### Improvements: + +- relax ash version requirement + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.2](https://github.com/ash-project/ash_postgres/compare/v0.41.1...v0.41.2) (2021-11-10) + +### Bug Fixes: + +- copy query prefix to newly created query (#74) + +### Improvements: + +- add custom migration types, and repo level override + +- update to latest version of ash + +## [v0.41.1](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.9...v0.41.1) (2021-11-03) + +### Bug Fixes: + +- copy query prefix to newly created query (#74) + +### Improvements: + +- update to latest version of ash + +## [v0.41.0-rc.9](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.8...v0.41.0-rc.9) (2021-11-01) + +### Bug Fixes: + +- use proper ecto types everywhere + +- try to fix missing paren issue in array_agg + +- fix can? for :joins (#73) + +- remove unused default value + +- use proper identity names for polymorphic resources + +- set identity names propertly for polymorphic resources + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `default` on aggregates + +- support `custom_indexes` + +## [v0.41.0-rc.8](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.7...v0.41.0-rc.8) (2021-10-25) + +### Bug Fixes: + +- fix can? for :joins (#73) + +- remove unused default value + +- use proper identity names for polymorphic resources + +- set identity names propertly for polymorphic resources + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `default` on aggregates + +- support `custom_indexes` + +## [v0.41.0-rc.7](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.6...v0.41.0-rc.7) (2021-10-24) + +### Bug Fixes: + +- fix can? for :joins (#73) + +- remove unused default value + +- use proper identity names for polymorphic resources + +- set identity names propertly for polymorphic resources + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.6](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.5...v0.41.0-rc.6) (2021-09-26) + +### Bug Fixes: + +- remove unused default value + +- use proper identity names for polymorphic resources + +- set identity names propertly for polymorphic resources + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.5](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.4...v0.41.0-rc.5) (2021-09-21) + +### Bug Fixes: + +- use proper identity names for polymorphic resources + +- set identity names propertly for polymorphic resources + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.4](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.3...v0.41.0-rc.4) (2021-09-21) + +### Bug Fixes: + +- set identity names propertly for polymorphic resources + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.3](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.2...v0.41.0-rc.3) (2021-09-21) + +### Bug Fixes: + +- handle nil values in snapshots better + +- remove unused field from snapshot parsing + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.2](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.1...v0.41.0-rc.2) (2021-09-21) + +### Bug Fixes: + +- remove unused field from snapshot parsing + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.1](https://github.com/ash-project/ash_postgres/compare/v0.41.0-rc.0...v0.41.0-rc.1) (2021-09-20) + +### Improvements: + +- support `custom_indexes` + +## [v0.41.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.40.11...v0.41.0-rc.0) (2021-09-13) + +### Breaking Changes: + +- update to latest ash/ecto versions w/ parameterized types + +### Improvements: + +- Support default tenant migration path in releases (#69) + +## [v0.40.11](https://github.com/ash-project/ash_postgres/compare/v0.40.10...v0.40.11) (2021-07-28) + +### Bug Fixes: + +- set subquery prefix properly + +## [v0.40.10](https://github.com/ash-project/ash_postgres/compare/v0.40.9...v0.40.10) (2021-07-27) + +### Bug Fixes: + +- set subquery source correctly + +- create parameter for ci strings + +- explicitly set prefix at each level + +- interaction w/ attribute and context tenancy + +### Improvements: + +- info on migration generator output + +- use match: :full on attr multitenancy + +- update to latest ash + +- update to latest ash + +- upgrade ash dep + +## [v0.40.9](https://github.com/ash-project/ash_postgres/compare/v0.40.8...v0.40.9) (2021-07-22) + +### Bug Fixes: + +- don't add a non-list to a list + +### Improvements: + +- add sort + select test + +## [v0.40.8](https://github.com/ash-project/ash_postgres/compare/v0.40.7...v0.40.8) (2021-07-19) + +### Bug Fixes: + +- ensure source table is sorted in lateral join + +### Improvements: + +- fix significant performance issue in lateral joins + +## [v0.40.7](https://github.com/ash-project/ash_postgres/compare/v0.40.6...v0.40.7) (2021-07-12) + +### Improvements: + +- support default_prefix configuration + +## [v0.40.6](https://github.com/ash-project/ash_postgres/compare/v0.40.5...v0.40.6) (2021-07-08) + +### Bug Fixes: + +- fix migrator mix tasks w/ only/except tenants + +- drop foreign keys after table create properly + +- drop foreign keys before dropping table + +- left_lateral_join for many_to_many aggregates + +- properly reference nested aggregate fields for join + +- properly determine fallback table for polymorphic resources + +- ensure non-tenant resources can be aggregates + +- properly set aggregate query sources + +- retain parent as bindings + +- don't add `rel_source` at all + +- properly build atoms list + +- horribly hack ecto for dynamic bindings + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- `--name` when generating migrations + +- add `mix ash_postgres.rollback` + +- update to latest ash + +- update to latest ash + +- leverage new `private_vars` for errs + +## [v0.40.5](https://github.com/ash-project/ash_postgres/compare/v0.40.4...v0.40.5) (2021-07-08) + +### Bug Fixes: + +- fix migrator mix tasks w/ only/except tenants + +- drop foreign keys after table create properly + +- drop foreign keys before dropping table + +- left_lateral_join for many_to_many aggregates + +- properly reference nested aggregate fields for join + +- properly determine fallback table for polymorphic resources + +- ensure non-tenant resources can be aggregates + +- properly set aggregate query sources + +- retain parent as bindings + +- don't add `rel_source` at all + +- properly build atoms list + +- horribly hack ecto for dynamic bindings + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- add `mix ash_postgres.rollback` + +- update to latest ash + +- update to latest ash + +- leverage new `private_vars` for errs + +## [v0.40.4](https://github.com/ash-project/ash_postgres/compare/v0.40.3...v0.40.4) (2021-07-05) + +### Bug Fixes: + +- left_lateral_join for many_to_many aggregates + +- properly reference nested aggregate fields for join + +- properly determine fallback table for polymorphic resources + +- ensure non-tenant resources can be aggregates + +- properly set aggregate query sources + +- retain parent as bindings + +- don't add `rel_source` at all + +- properly build atoms list + +- horribly hack ecto for dynamic bindings + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- update to latest ash + +- update to latest ash + +- leverage new `private_vars` for errs + +## [v0.40.3](https://github.com/ash-project/ash_postgres/compare/v0.40.2...v0.40.3) (2021-07-03) + +### Bug Fixes: + +- ensure non-tenant resources can be aggregates + +- properly set aggregate query sources + +- retain parent as bindings + +- don't add `rel_source` at all + +- properly build atoms list + +- horribly hack ecto for dynamic bindings + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- update to latest ash + +- leverage new `private_vars` for errs + +## [v0.40.2](https://github.com/ash-project/ash_postgres/compare/v0.40.1...v0.40.2) (2021-07-02) + +### Bug Fixes: + +- properly set aggregate query sources + +- retain parent as bindings + +- don't add `rel_source` at all + +- properly build atoms list + +- horribly hack ecto for dynamic bindings + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- update to latest ash + +- leverage new `private_vars` for errs + +## [v0.40.1](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc5...v0.40.1) (2021-07-02) + +### Bug Fixes: + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- update to latest ash + +- leverage new `private_vars` for errs + +## [v0.40.0-rc5](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc4...v0.40.0-rc5) (2021-07-01) + +### Bug Fixes: + +- properly coalesce aggregate values + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- leverage new `private_vars` for errs + +## [v0.40.0-rc4](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc3...v0.40.0-rc4) (2021-06-23) + +### Bug Fixes: + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +### Improvements: + +- leverage new `private_vars` for errs + +## [v0.40.0-rc3](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc2...v0.40.0-rc3) (2021-06-15) + +### Bug Fixes: + +- always add nullability flag + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +## [v0.40.0-rc2](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc1...v0.40.0-rc2) (2021-06-08) + +### Bug Fixes: + +- sort references only after other same-table ops + +- generate multitenant foreign keys properly + +## [v0.40.0-rc1](https://github.com/ash-project/ash_postgres/compare/v0.40.0-rc.0...v0.40.0-rc1) (2021-06-05) + +## [v0.39.0-rc.0](https://github.com/ash-project/ash_postgres/compare/v0.38.11...v0.39.0-rc.0) (2021-06-04) + +### Features: + +- support expression based calculations + +- support concat + if expressions + +### Improvements: + +- various other improvements + +## [v0.38.11](https://github.com/ash-project/ash_postgres/compare/v0.38.10...v0.38.11) (2021-05-23) + +### Bug Fixes: + +- set prefix to "public" for fkeys to public schema + +### Improvements: + +- set explicit prefix on join filters + +## [v0.38.10](https://github.com/ash-project/ash_postgres/compare/v0.38.9...v0.38.10) (2021-05-19) + +### Improvements: + +- support new ash upsert specifying targets + +- update to latest ash + +## [v0.38.9](https://github.com/ash-project/ash_postgres/compare/v0.38.8...v0.38.9) (2021-05-12) + +### Bug Fixes: + +- properly group many_to_many aggregates + +## [v0.38.8](https://github.com/ash-project/ash_postgres/compare/v0.38.7...v0.38.8) (2021-05-09) + +### Improvements: + +- update to the latest ash version + +## [v0.38.7](https://github.com/ash-project/ash_postgres/compare/v0.38.6...v0.38.7) (2021-05-09) + +### Improvements: + +- support latest ash/filtering on related aggregates + +## [v0.38.6](https://github.com/ash-project/ash_postgres/compare/v0.38.5...v0.38.6) (2021-05-07) + +### Bug Fixes: + +- properly construct sources for lateral joins + +- copy the correct data for lateral join queries + +- better errors in error cases + +### Improvements: + +- update to latest ash + +## [v0.38.5](https://github.com/ash-project/ash_postgres/compare/v0.38.4...v0.38.5) (2021-05-07) + +### Bug Fixes: + +- don't cast booleans to string in last_ditch_cast + +## [v0.38.4](https://github.com/ash-project/ash_postgres/compare/v0.38.3...v0.38.4) (2021-05-07) + +### Improvements: + +- support latest ash version resource sorts + +## [v0.38.3](https://github.com/ash-project/ash_postgres/compare/v0.38.2...v0.38.3) (2021-05-06) + +### Improvements: + +- update to latest ash + +- document script to iterate migrations (#65) + +## [v0.38.2](https://github.com/ash-project/ash_postgres/compare/v0.38.1...v0.38.2) (2021-05-04) + +### Bug Fixes: + +- join to join table in lateral join query + +- multitenancy + lateral join sources + +- don't distinct in lateral joins + +## [v0.38.1](https://github.com/ash-project/ash_postgres/compare/v0.38.0...v0.38.1) (2021-05-04) + +### Bug Fixes: + +- fix fragment processing broken (#64) + +## [v0.38.0](https://github.com/ash-project/ash_postgres/compare/v0.37.8...v0.38.0) (2021-04-29) + +### Features: + +- support new side load improvements + +### Improvements: + +- Preserve attribute order (#63) + +## [v0.37.8](https://github.com/ash-project/ash_postgres/compare/v0.37.7...v0.37.8) (2021-04-27) + +### Bug Fixes: + +- simpler index names + +- don't prefix unique indices with prefix() + +- sort index operations last + +### Improvements: + +- custom index names + +## [v0.37.7](https://github.com/ash-project/ash_postgres/compare/v0.37.6...v0.37.7) (2021-04-27) + +### Bug Fixes: + +- remove inspects that were left in by accident + +## [v0.37.6](https://github.com/ash-project/ash_postgres/compare/v0.37.5...v0.37.6) (2021-04-27) + +### Bug Fixes: + +- type cast atoms to strings in last ditch cast + +- properly type cast + +- Remove duplicate file extension (#60) + +## [v0.37.5](https://github.com/ash-project/ash_postgres/compare/v0.37.4...v0.37.5) (2021-04-27) + +### Bug Fixes: + +- properly type cast + +## [v0.37.4](https://github.com/ash-project/ash_postgres/compare/v0.37.3...v0.37.4) (2021-04-26) + +### Improvements: + +- support `list` aggregate + +## [v0.37.3](https://github.com/ash-project/ash_postgres/compare/v0.37.2...v0.37.3) (2021-04-26) + +### Bug Fixes: + +- stringify struct defaults in migration generator + +- properly comment out extension uninstallation code + +## [v0.37.2](https://github.com/ash-project/ash_postgres/compare/v0.37.1...v0.37.2) (2021-04-21) + +### Improvements: + +- support ash enums + +## [v0.37.1](https://github.com/ash-project/ash_postgres/compare/v0.37.0...v0.37.1) (2021-04-19) + +### Bug Fixes: + +- include type in references (because it is _not_ automatic) + +## [v0.37.0](https://github.com/ash-project/ash_postgres/compare/v0.36.5...v0.37.0) (2021-04-19) + +### Features: + +- add check_constraints, both for validation and migrations + +## [v0.36.5](https://github.com/ash-project/ash_postgres/compare/v0.36.4...v0.36.5) (2021-04-13) + +### Bug Fixes: + +- always drop constraints before modifying + +- properly compare old references and new references + +## [v0.36.4](https://github.com/ash-project/ash_postgres/compare/v0.36.3...v0.36.4) (2021-04-12) + +### Bug Fixes: + +- don't explicitly set type in `references` + +### Improvements: + +- default integers to `:bigint` + +## [v0.36.3](https://github.com/ash-project/ash_postgres/compare/v0.36.2...v0.36.3) (2021-04-12) + +### Improvements: + +- primary autoincrement key as bigserial (#54) + +## [v0.36.2](https://github.com/ash-project/ash_postgres/compare/v0.36.1...v0.36.2) (2021-04-09) + +### Improvements: + +- support new ash select feature + +## [v0.36.1](https://github.com/ash-project/ash_postgres/compare/v0.36.0...v0.36.1) (2021-04-04) + +### Bug Fixes: + +- raise when `all_tenants/0` default impl is called + +### Improvements: + +- add sum aggregate (#53) + +## [v0.36.0](https://github.com/ash-project/ash_postgres/compare/v0.35.5...v0.36.0) (2021-04-01) + +### Features: + +- support configuring references + +- support configuring polymorphic references + +- support `distinct` Ash queries + +## [v0.35.5](https://github.com/ash-project/ash_postgres/compare/v0.35.4...v0.35.5) (2021-03-29) + +### Bug Fixes: + +- Made AshPostgres.Repo.init/2 overridable (#51) + +### Improvements: + +- only count resources w/ create action for nullability + +- better error message on missing table + +## [v0.35.4](https://github.com/ash-project/ash_postgres/compare/v0.35.3...v0.35.4) (2021-03-21) + +### Bug Fixes: + +- reroute `Ash.Type.UUID` to `:uuid` in migrations + +- force create extensions snapshot + +### Improvements: + +- consistent foreign key names + +- support custom foreign key error messages + +## [v0.35.3](https://github.com/ash-project/ash_postgres/compare/v0.35.2...v0.35.3) (2021-03-19) + +### Bug Fixes: + +- force create extensions snapshot + +- more conservative inner join checks + +- add back in inner join detection logic + +### Improvements: + +- consistent foreign key names + +- support custom foreign key error messages + +## [v0.35.2](https://github.com/ash-project/ash_postgres/compare/v0.35.1...v0.35.2) (2021-03-05) + +### Bug Fixes: + +- more conservative inner join checks + +- add back in inner join detection logic + +## [v0.35.1](https://github.com/ash-project/ash_postgres/compare/v0.35.0...v0.35.1) (2021-03-02) + +### Bug Fixes: + +- don't start the whole app in migrate + +## [v0.35.0](https://github.com/ash-project/ash_postgres/compare/v0.34.7...v0.35.0) (2021-03-02) + +### Features: + +- automatically install extensions from repo + +## [v0.34.7](https://github.com/ash-project/ash_postgres/compare/v0.34.6...v0.34.7) (2021-03-02) + +### Bug Fixes: + +- typo in references for multitenancy + +- `null: true` when attr isn't on all resources for a table + +## [v0.34.6](https://github.com/ash-project/ash_postgres/compare/v0.34.5...v0.34.6) (2021-02-24) + +### Bug Fixes: + +- better embedded filters, switch to latest ash + +## [v0.34.5](https://github.com/ash-project/ash_postgres/compare/v0.34.4...v0.34.5) (2021-02-23) + +### Improvements: + +- support latest ash + +## [v0.34.4](https://github.com/ash-project/ash_postgres/compare/v0.34.3...v0.34.4) (2021-02-08) + +### Bug Fixes: + +- trim when choosing new attribute name + +## [v0.34.3](https://github.com/ash-project/ash_postgres/compare/v0.34.2...v0.34.3) (2021-02-06) + +### Bug Fixes: + +- don't reference polymorphic tables to belongs_to relationships + +## [v0.34.2](https://github.com/ash-project/ash_postgres/compare/v0.34.1...v0.34.2) (2021-02-06) + +### Bug Fixes: + +- set up references properly + +## [v0.34.1](https://github.com/ash-project/ash_postgres/compare/v0.34.0...v0.34.1) (2021-02-06) + +### Bug Fixes: + +- reference the configured table if set + +## [v0.34.0](https://github.com/ash-project/ash_postgres/compare/v0.33.1...v0.34.0) (2021-02-06) + +### Features: + +- support polymorphic relationships + +## [v0.33.1](https://github.com/ash-project/ash_postgres/compare/v0.33.0...v0.33.1) (2021-01-27) + +### Bug Fixes: + +- actually insert the tracking row + +## [v0.33.0](https://github.com/ash-project/ash_postgres/compare/v0.32.2...v0.33.0) (2021-01-27) + +### Features: + +- add `mix ash_postgres.create` + +- add `mix ash_postgres.migrate` + +- add `mix ash_postgres.migrate --tenants` + +- add `mix ash_postgres.drop` + +### Bug Fixes: + +- rework the way multitenant migrations work + +## [v0.32.2](https://github.com/ash-project/ash_postgres/compare/v0.32.1...v0.32.2) (2021-01-26) + +### Bug Fixes: + +- un-break the `in` filter type casting code + +### Improvements: + +- better errors for multitenant unique constraints + +## [v0.32.1](https://github.com/ash-project/ash_postgres/compare/v0.32.0...v0.32.1) (2021-01-24) + +### Bug Fixes: + +- `ago` was adding, not subtracting + +## [v0.32.0](https://github.com/ash-project/ash_postgres/compare/v0.31.1...v0.32.0) (2021-01-24) + +### Features: + +- support latest ash + contains + +## [v0.31.1](https://github.com/ash-project/ash_postgres/compare/v0.31.0...v0.31.1) (2021-01-22) + +### Improvements: + +- update to latest ash + +## [v0.31.0](https://github.com/ash-project/ash_postgres/compare/v0.30.1...v0.31.0) (2021-01-22) + +### Features: + +- support fragments + +- support type casting + +- update to latest ash to support expressions + +### Bug Fixes: + +- update CI versions + +## [v0.30.1](https://github.com/ash-project/ash_postgres/compare/v0.30.0...v0.30.1) (2021-01-13) + +## [v0.30.0](https://github.com/ash-project/ash_postgres/compare/v0.29.6...v0.30.0) (2021-01-13) + +### Features: + +- Add check_migrated option to migration generator (#40) (#43) + +## [v0.29.6](https://github.com/ash-project/ash_postgres/compare/v0.29.5...v0.29.6) (2021-01-12) + +### Bug Fixes: + +- rename out of phase, small migration fix + +## [v0.29.5](https://github.com/ash-project/ash_postgres/compare/v0.29.4...v0.29.5) (2021-01-10) + +### Improvements: + +- Use ecto_sql formatter settings (#38) + +## [v0.29.4](https://github.com/ash-project/ash_postgres/compare/v0.29.3...v0.29.4) (2021-01-10) + +### Improvements: + +- Omit field opts if they are default values (#37) + +## [v0.29.3](https://github.com/ash-project/ash_postgres/compare/v0.29.2...v0.29.3) (2021-01-08) + +### Improvements: + +- support latest ash + +## [v0.29.2](https://github.com/ash-project/ash_postgres/compare/v0.29.1...v0.29.2) (2021-01-08) + +### Improvements: + +- Make integer serial if generated + +## [v0.29.1](https://github.com/ash-project/ash_postgres/compare/v0.29.0...v0.29.1) (2021-01-08) + +### Improvements: + +- support latest ash version + +## [v0.29.0](https://github.com/ash-project/ash_postgres/compare/v0.28.1...v0.29.0) (2021-01-08) + +### Features: + +- retain snapshot history + +### Improvements: + +- support latest ash version + +## [v0.28.1](https://github.com/ash-project/ash_postgres/compare/v0.28.0...v0.28.1) (2021-01-07) + +### Improvements: + +- Add :binary migration type (#33) + +## [v0.28.0](https://github.com/ash-project/ash_postgres/compare/v0.27.0...v0.28.0) (2020-12-29) + +### Features: + +- support latest Ash version + +## [v0.27.0](https://github.com/ash-project/ash_postgres/compare/v0.26.2...v0.27.0) (2020-12-23) + +### Features: + +- support refs on both sides of operators + +### Bug Fixes: + +- bump ash version + +## [v0.26.2](https://github.com/ash-project/ash_postgres/compare/v0.26.1...v0.26.2) (2020-12-06) + +### Bug Fixes: + +- properly accept the `tenant_migration_path` + +## [v0.26.1](https://github.com/ash-project/ash_postgres/compare/v0.26.0...v0.26.1) (2020-12-01) + +### Bug Fixes: + +- set default properly when modifying + +## [v0.26.0](https://github.com/ash-project/ash_postgres/compare/v0.25.5...v0.26.0) (2020-11-25) + +### Features: + +- don't drop columns unless explicitly told to + +### Bug Fixes: + +- various migration generator bug fixes + +## [v0.25.5](https://github.com/ash-project/ash_postgres/compare/v0.25.4...v0.25.5) (2020-11-17) + +### Bug Fixes: + +- drop constraints outside of phases (#29) + +## [v0.25.4](https://github.com/ash-project/ash_postgres/compare/v0.25.3...v0.25.4) (2020-11-07) + +### Bug Fixes: + +- only alter the things that have changed + +## [v0.25.3](https://github.com/ash-project/ash_postgres/compare/v0.25.2...v0.25.3) (2020-11-06) + +### Improvements: + +- add utc_datetime migration type + +## [v0.25.2](https://github.com/ash-project/ash_postgres/compare/v0.25.1...v0.25.2) (2020-11-03) + +### Bug Fixes: + +- access data_layer_query with function + +## [v0.25.1](https://github.com/ash-project/ash_postgres/compare/v0.25.0...v0.25.1) (2020-10-29) + +### Improvements: + +- mark repo as not requiring compile-time dep + +## [v0.25.0](https://github.com/ash-project/ash_postgres/compare/v0.24.0...v0.25.0) (2020-10-29) + +### Features: + +- multitenancy (#25) + +### Bug Fixes: + +- verify repo using ensure_compiled + +## [v0.24.0](https://github.com/ash-project/ash_postgres/compare/v0.23.2...v0.24.0) (2020-10-17) + +### Features: + +- support latest ash + +## [v0.23.2](https://github.com/ash-project/ash_postgres/compare/v0.23.1...v0.23.2) (2020-10-07) + +## [v0.23.1](https://github.com/ash-project/ash_postgres/compare/v0.23.0...v0.23.1) (2020-10-06) + +## [v0.23.0](https://github.com/ash-project/ash_postgres/compare/v0.22.1...v0.23.0) (2020-10-06) + +### Features: + +- update to latest ash, trigram filter + +## [v0.22.1](https://github.com/ash-project/ash_postgres/compare/v0.22.0...v0.22.1) (2020-10-01) + +### Bug Fixes: + +- don't group alters with creates (#22) + +- add jason dependency, clean lockfile (#21) + +## [v0.22.0](https://github.com/ash-project/ash_postgres/compare/v0.21.0...v0.22.0) (2020-09-24) + +### Features: + +- fix error when filtering with `true` + +### Bug Fixes: + +- broken types for `in` operator + +## [v0.21.0](https://github.com/ash-project/ash_postgres/compare/v0.20.1...v0.21.0) (2020-09-19) + +### Features: + +- support base_filter (#18) + +## [v0.20.1](https://github.com/ash-project/ash_postgres/compare/v0.20.0...v0.20.1) (2020-09-11) + +### Bug Fixes: + +- document/update migration path logic + +## [v0.20.0](https://github.com/ash-project/ash_postgres/compare/v0.19.0...v0.20.0) (2020-09-11) + +### Features: + +- snapshot-based migration generator + +## [v0.19.0](https://github.com/ash-project/ash_postgres/compare/v0.18.0...v0.19.0) (2020-09-02) + +### Features: + +- support inner joins when possible (#15) + +### Bug Fixes: + +- better support for aggregates/calculations when delegating + +- don't fail w/ no extensions configured + +## [v0.18.0](https://github.com/ash-project/ash_postgres/compare/v0.17.0...v0.18.0) (2020-08-26) + +### Features: + +- update to ash 1.11 (#13) + +- support Ash v1.10 (#12) + +- support latest ash + +- update to latest ash + +## [v0.17.0](https://github.com/ash-project/ash_postgres/compare/v0.16.1...v0.17.0) (2020-08-26) + +### Features: + +- update to ash 1.11 (#13) + +- support Ash v1.10 (#12) + +- support latest ash + +- update to latest ash + +## [v0.16.1](https://github.com/ash-project/ash_postgres/compare/v0.16.0...v0.16.1) (2020-08-19) + +### Bug Fixes: + +- fix compile/dialyzer issues + +## [v0.16.0](https://github.com/ash-project/ash_postgres/compare/v0.15.0...v0.16.0) (2020-08-19) + +### Features: + +- update to latest ash + +- update to latest version of ash + +## [v0.15.0](https://github.com/ash-project/ash_postgres/compare/v0.14.0...v0.15.0) (2020-08-18) + +### Features: + +- update to latest version of ash + +## [v0.14.0](https://github.com/ash-project/ash_postgres/compare/v0.13.0...v0.14.0) (2020-08-17) + +### Features: + +- support ash 1.7 + +- support named aggregates + +## [v0.13.0](https://github.com/ash-project/ash_postgres/compare/v0.12.1...v0.13.0) (2020-07-25) + +### Features: + +- update to latest ash + +- support latest ash + +## [v0.12.1](https://github.com/ash-project/ash_postgres/compare/v0.12.0...v0.12.1) (2020-07-24) + +### Bug Fixes: + +- add can? for `:aggregate` + +## [v0.12.0](https://github.com/ash-project/ash_postgres/compare/0.11.2...v0.12.0) (2020-07-24) + +### Features: + +- update to latest ash + +## [v0.11.2](https://github.com/ash-project/ash_postgres/compare/0.11.1...v0.11.2) (2020-07-23) + +### Bug Fixes: + +## [v0.11.1](https://github.com/ash-project/ash_postgres/compare/0.11.0...v0.11.1) (2020-07-23) + +### Bug Fixes: + +## [v0.11.0](https://github.com/ash-project/ash_postgres/compare/0.10.0...v0.11.0) (2020-07-23) + +### Features: + +- support ash 13.0 aggregates + +## [v0.10.0](https://github.com/ash-project/ash_postgres/compare/0.9.0...v0.10.0) (2020-07-15) + +### Features: + +- update to latest ash + +## [v0.9.0](https://github.com/ash-project/ash_postgres/compare/0.8.0...v0.9.0) (2020-07-13) + +### Features: + +- update to latest ash + +## [v0.8.0](https://github.com/ash-project/ash_postgres/compare/0.7.0...v0.8.0) (2020-07-09) + +### Features: + +- update to latest ash + +## [v0.7.0](https://github.com/ash-project/ash_postgres/compare/0.6.0...v0.7.0) (2020-07-09) + +### Features: + +- update to latest ash + +- update to latest ash, add docs + +- update to ash 0.9.1 for transactions + +## [v0.6.0](https://github.com/ash-project/ash_postgres/compare/0.5.0...v0.6.0) (2020-06-29) + +### Features: + +- update to latest ash + +## [v0.5.0](https://github.com/ash-project/ash_postgres/compare/0.4.0...v0.5.0) (2020-06-29) + +### Features: + +- upgrade to latest ash + +## [v0.4.0](https://github.com/ash-project/ash_postgres/compare/0.3.0...v0.4.0) (2020-06-27) + +### Features: + +- update to latest ash + +## [v0.3.0](https://github.com/ash-project/ash_postgres/compare/0.2.1...v0.3.0) (2020-06-19) + +### Features: + +- New filter style (#10) + +## [v0.2.1](https://github.com/ash-project/ash_postgres/compare/0.2.0...v0.2.1) (2020-06-15) + +### Bug Fixes: + +- update .formatter.exs + +## [v0.2.0](https://github.com/ash-project/ash_postgres/compare/0.1.4...v0.2.0) (2020-06-14) + +### Features: + +- use the new DSL builder for config (#7) + +## [v0.1.4](https://github.com/ash-project/ash_postgres/compare/0.1.3...v0.1.4) (2020-06-05) + +### Bug Fixes: + +- update ash version dependency + +- account for removal of name + +## [v0.1.3](https://github.com/ash-project/ash_postgres/compare/0.1.2...v0.1.3) (2020-06-03) + +This release was a test of our automatic hex.pm package deployment + +## Begin Changelog From 8ce969317f6e389d4d8cc04e924ba02b15d237b2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 8 May 2024 20:30:37 -0400 Subject: [PATCH 0419/1215] docs: add additional context for nothing & restrict --- documentation/topics/resources/references.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/documentation/topics/resources/references.md b/documentation/topics/resources/references.md index fd7860b9..1922e480 100644 --- a/documentation/topics/resources/references.md +++ b/documentation/topics/resources/references.md @@ -12,12 +12,20 @@ end > ### Actions are not used for this behavior {: .warning} > -> No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens *directly* in the database. +> No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens _directly_ in the database. ## Nothing vs Restrict -The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). `:restrict` will prevent the deletion from happening *before* the end of the database transaction, whereas `:nothing` allows the transaction to complete before doing so. This allows for things like updating or deleting the destination row and *then* updating updating or deleting the reference(as long as you are in a transaction). +```elixir +references do + reference :post, on_delete: :nothing + # vs + reference :post, on_delete: :restrict +end +``` + +The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). `:restrict` will prevent the deletion from happening _before_ the end of the database transaction, whereas `:nothing` allows the transaction to complete before doing so. This allows for things like updating or deleting the destination row and _then_ updating updating or deleting the reference(as long as you are in a transaction). The reason that `:nothing` still ultimately prevents deletion is because postgres enforces foreign key referential integrity. ## On Delete -This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, *not* a `destroy` action in your resource. See the warning above. +This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, _not_ a `destroy` action in your resource. See the warning above. From 590fb64db353c40f9ca558716d3d1fa43501b373 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 9 May 2024 14:31:54 -0400 Subject: [PATCH 0420/1215] fix: ensure that `codegen` dry run works --- lib/data_layer.ex | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index f36ded27..03280713 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -501,15 +501,6 @@ defmodule AshPostgres.DataLayer do end def codegen(args) do - {args, _, _} = OptionParser.parse(args, strict: [name: :string]) - - args = - if args[:name] do - ["--name", to_string(args[:name])] - else - [] - end - Mix.Task.run("ash_postgres.generate_migrations", args) end From df53ef86135d91464ec7fb7e48cd9e9878d3f37b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 10 May 2024 17:20:05 -0400 Subject: [PATCH 0421/1215] chore: release version v2.0.0 --- mix.exs | 6 +++--- mix.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 6ec34343..97afcc0c 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.0-rc.15" + @version "2.0.0" def project do [ @@ -162,8 +162,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0.0-rc and >= 3.0.0-rc.38")}, - {:ash_sql, ash_sql_version("~> 0.1.1-rc and >= 0.1.1-rc.18")}, + {:ash, ash_version("~> 3.0")}, + {:ash_sql, ash_sql_version("~> 0.1")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 7cb39b17..c8bc153c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0-rc.46", "8c84ca24003c3e678f84f51da79a6b3edd1d808ccace53e207776e7d15a8c5f7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c833ba90e76a17cf5b9386bc47626d943f5da0908a5e7b850433f9db3e79784c"}, + "ash": {:hex, :ash, "3.0.0", "2ef88639fce9f126c57c115f955d7e29919942afe74adc00cb7250fd7ced9f5f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ffed4651c9faf79e90066afdd52202e3f8951624bf73fd8ad34aa4c22fceef4b"}, "ash_sql": {:hex, :ash_sql, "0.1.1-rc.19", "1547c877de40c9b7c0e53ce33768e04e7f6dac4fb9217b453b9754592589a960", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "3c4ee1b8cf7755e637618a79443e92681a8350cb729027240651c244018dfb42"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -27,10 +27,10 @@ "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, - "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"}, + "reactor": {:hex, :reactor, "0.8.2", "b2be82b1c3402537d06a8f85bb1849f72cb6b4be140495cb8956de7aec2fdebd", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c35eb23b77cc77ba922af108722ac93257899e35cfdd18882f0e659ad2cac9f3"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"}, + "sourceror": {:hex, :sourceror, "1.1.0", "9c129fa1bd7290014acf6f73e292f43938c17e3fccd7b7df6f41122cab45dda9", [:mix], [], "hexpm", "b9c348688e2cfc20acfef0feaca88643044be5acd2e0b02cf4a8d6ac1edc4c4a"}, "spark": {:hex, :spark, "2.1.20", "204db8fd28378783c28a9dcb0bebdaf1d51b14a9ea106e1080457d29510a66ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e7a4f8f8ca7a477918af1eb65e20f2015f783a9a23e5f73d1020edf5b2ef69be"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 58f09e41c5e330691113a5106c1f37831f63b663 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 02:02:51 -0400 Subject: [PATCH 0422/1215] chore(deps-dev): bump credo from 1.7.5 to 1.7.6 (#275) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c8bc153c..a22b3947 100644 --- a/mix.lock +++ b/mix.lock @@ -4,7 +4,7 @@ "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, - "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, + "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, From 66b2cfd0c7323cadecd7ff58eb6c5a7037749ceb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 02:06:05 -0400 Subject: [PATCH 0423/1215] chore(deps): bump ash_sql from 0.1.1-rc.19 to 0.1.2 (#276) Bumps [ash_sql](https://github.com/ash-project/ash_sql) from 0.1.1-rc.19 to 0.1.2. - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.1.1-rc.19...v0.1.2) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index a22b3947..cabc437b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.0", "2ef88639fce9f126c57c115f955d7e29919942afe74adc00cb7250fd7ced9f5f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ffed4651c9faf79e90066afdd52202e3f8951624bf73fd8ad34aa4c22fceef4b"}, - "ash_sql": {:hex, :ash_sql, "0.1.1-rc.19", "1547c877de40c9b7c0e53ce33768e04e7f6dac4fb9217b453b9754592589a960", [:mix], [{:ash, "~> 3.0.0-rc", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "3c4ee1b8cf7755e637618a79443e92681a8350cb729027240651c244018dfb42"}, + "ash_sql": {:hex, :ash_sql, "0.1.2", "a479348d46bf19ec0c981432a47ae8f15781d559bfc708a0260853acb8cc848b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "35fab32c2e5316fac8552dd541707301c616bf548735af593581dcebc76215eb"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 9e2c01693fcbd43893c1c48d69abf2ee2943acbe Mon Sep 17 00:00:00 2001 From: Nicholas Moen Date: Sat, 11 May 2024 20:20:52 -0600 Subject: [PATCH 0424/1215] docs: change 'Get Started' guide for Ash 3.0 compatibility (#277) --- .../get-started-with-ash-postgres.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index a8235e9b..5c3326ee 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -25,7 +25,7 @@ In this guide we will: Add the `:ash_postgres` dependency to your application -`{:ash_postgres, "~> 1.3.6"}` +`{:ash_postgres, "~> 2.0.0"}` Add `:ash_postgres` to your `.formatter.exs` file @@ -158,7 +158,7 @@ And finally, add the repo to your application Now we can add the data layer to our resources. The basic configuration for a resource requires the `d:AshPostgres.postgres|table` and the `d:AshPostgres.postgres|repo`. ```elixir -# in lib/helpdesk/support/resources/ticket.ex +# in lib/helpdesk/support/ticket.ex use Ash.Resource, domain: Helpdesk.Support, @@ -171,7 +171,7 @@ Now we can add the data layer to our resources. The basic configuration for a re ``` ```elixir -# in lib/helpdesk/support/resources/representative.ex +# in lib/helpdesk/support/representative.ex use Ash.Resource, domain: Helpdesk.Support, @@ -217,7 +217,7 @@ require Ash.Query representative = ( Helpdesk.Support.Representative |> Ash.Changeset.for_create(:create, %{name: "Joe Armstrong"}) - |> Helpdesk.Support.create!() + |> Ash.create!() ) for i <- 0..5 do @@ -226,12 +226,12 @@ for i <- 0..5 do |> Ash.Changeset.for_create(:open, %{subject: "Issue #{i}"}) |> Helpdesk.Support.create!() |> Ash.Changeset.for_update(:assign, %{representative_id: representative.id}) - |> Helpdesk.Support.update!() + |> Ash.update!() if rem(i, 2) == 0 do ticket |> Ash.Changeset.for_update(:close) - |> Helpdesk.Support.update!() + |> Ash.update!() end end ``` @@ -244,7 +244,7 @@ require Ash.Query # Show the tickets where the subject contains "2" Helpdesk.Support.Ticket |> Ash.Query.filter(contains(subject, "2")) -|> Helpdesk.Support.read!() +|> Ash.read!() ``` ```elixir @@ -253,7 +253,7 @@ require Ash.Query # Show the tickets that are closed and their subject does not contain "4" Helpdesk.Support.Ticket |> Ash.Query.filter(status == :closed and not(contains(subject, "4"))) -|> Helpdesk.Support.read!() +|> Ash.read!() ``` And, naturally, now that we are storing this in postgres, this database is persisted even if we stop/start our application. The nice thing, however, is that this was the _exact_ same code that we ran against our resources when they were backed by ETS. @@ -265,7 +265,7 @@ Lets add some aggregates to our representatives resource. Aggregates are a tool Here we will add an aggregate to easily query how many tickets are assigned to a representative, and how many of those tickets are open/closed. ```elixir -# in lib/helpdesk/support/resources/representative.ex +# in lib/helpdesk/support/representative.ex aggregates do # The first argument here is the name of the aggregate @@ -293,7 +293,7 @@ require Ash.Query Helpdesk.Support.Representative |> Ash.Query.filter(closed_tickets < 4) |> Ash.Query.sort(closed_tickets: :desc) -|> Helpdesk.Support.read!() +|> Ash.read!() ``` You can also load individual aggregates on demand after queries have already been run, and minimal SQL will be issued to run the aggregate. @@ -305,7 +305,7 @@ require Ash.Query representatives = Helpdesk.Support.read!(Helpdesk.Support.Representative) -Helpdesk.Support.load!(representatives, :open_tickets) +Ash.load!(representatives, :open_tickets) ``` ### Calculations @@ -315,7 +315,7 @@ Calculations can be pushed down into SQL in the same way. Calculations are simil For example, we can determine the percentage of tickets that are open: ```elixir -# in lib/helpdesk/support/resources/representative.ex +# in lib/helpdesk/support/representative.ex calculations do calculate :percent_open, :float, expr(open_tickets / total_tickets ) @@ -331,7 +331,7 @@ Helpdesk.Support.Representative |> Ash.Query.filter(percent_open > 0.25) |> Ash.Query.sort(:percent_open) |> Ash.Query.load(:percent_open) -|> Helpdesk.Support.read!() +|> Ash.read!() ``` ### Rich Configuration Options @@ -344,6 +344,6 @@ Take a look at the DSL documentation for more information on what you can config - [Ecto's documentation](https://hexdocs.pm/ecto/Ecto.html). AshPostgres (and much of Ash itself) is made possible by the amazing Ecto. If you find yourself looking for escape hatches when using Ash or ways to work directly with your database, you will want to know how Ecto works. Ash and AshPostgres intentionally do not hide Ecto, and in fact encourages its use whenever you need an escape hatch. -- [Postgres' documentation](https://www.postgresql.org/docs/). Although AshPostgres makes things a lot easier, you generally can't get away with not understanding the basics of postgres and SQL. +- [Postgres' documentation](https://www.postgresql.org/docs/). Although AshPostgres makes things a lot easier, you should understand the basics of postgres and SQL. - [Ecto's Migration documentation](https://hexdocs.pm/ecto_sql/Ecto.Migration.html) read more about migrations. Even with the ash_postgres migration generator, you will very likely need to modify your own migrations some day. From 2421e6fc2cdc5cc63c4eeb358a7797c07524d96d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 12 May 2024 12:50:13 -0400 Subject: [PATCH 0425/1215] fix: properly parse previous version from migration generation --- lib/migration_generator/migration_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 33899e6f..424fdac6 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -254,7 +254,7 @@ defmodule AshPostgres.MigrationGenerator do {ext_name, _version, up_fn, _down_fn} when is_function(up_fn, 1) -> current_version = Enum.find_value(extensions_snapshot[:installed] || [], 0, fn name -> - with ["", "v" <> version] <- String.split(name, to_string(ext_name)), + with ["", "_v" <> version] <- String.split(name, to_string(ext_name)), {integer, ""} <- Integer.parse(version) do integer else From 11e8473a7cf3d4ca8860a53a313b04e54c7cf9f1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 12 May 2024 12:50:58 -0400 Subject: [PATCH 0426/1215] chore: release version v2.0.1 --- CHANGELOG.md | 6 ++++++ mix.exs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a03bbf4..a6d3e9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0...v2.0.1) (2024-05-12) + +### Bug Fixes: + +- [AshPostgres.MigrationGenerator] properly parse previous version of custom extensions when generating migrations + ## [v2.0.0](https://github.com/ash-project/ash_postgres/compare/v2.0.0...2.0) The changelog is starting over. Please see `/documentation/1.0-CHANGELOG.md` in GitHub for previous changelogs. diff --git a/mix.exs b/mix.exs index 97afcc0c..bdd80ab4 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.0" + @version "2.0.1" def project do [ From d0c7984a89e569fa9aee04cd677e89a2779dd544 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 12 May 2024 16:28:48 -0400 Subject: [PATCH 0427/1215] test: add tests for action filters in bulk update/destroy fix: ensure filter is included in stale record error message --- lib/data_layer.ex | 4 ++-- test/bulk_destroy_test.exs | 20 ++++++++++++++++++ test/bulk_update_test.exs | 37 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 8 ++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 03280713..a93de5ed 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1861,7 +1861,7 @@ defmodule AshPostgres.DataLayer do resource ) do handle_raised_error( - Ash.Error.Changes.StaleRecord.exception(resource: resource, filters: filters), + Ash.Error.Changes.StaleRecord.exception(resource: resource, filter: filters), stacktrace, context, resource @@ -2494,7 +2494,7 @@ defmodule AshPostgres.DataLayer do {:error, Ash.Error.Changes.StaleRecord.exception( resource: resource, - filters: changeset.filter + filter: changeset.filter )} {1, [result]} -> diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index 09d7e9da..b04ea246 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -28,6 +28,26 @@ defmodule AshPostgres.BulkDestroyTest do assert ["george"] = Ash.read!(Post) |> Enum.map(& &1.title) end + test "bulk destroys honor changeset filters" do + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.bulk_destroy!(:destroy_only_freds, %{}) + + # 😢 sad + assert ["george"] = Ash.read!(Post) |> Enum.map(& &1.title) + end + + test "bulk destroys honor changeset filters when streaming" do + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.bulk_destroy!(:destroy_only_freds, %{}, strategy: :stream) + + # 😢 sad + assert ["george"] = Ash.read!(Post) |> Enum.map(& &1.title) + end + test "the query can join to related tables when necessary" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index cc1b7ac2..44a91960 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -85,6 +85,43 @@ defmodule AshPostgres.BulkUpdateTest do assert titles == ["fred_stuff", "george"] end + test "bulk updates honor update action filters" do + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.bulk_update!(:update_only_freds, %{}, + return_errors?: true, + atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")} + ) + + titles = + Post + |> Ash.read!() + |> Enum.map(& &1.title) + |> Enum.sort() + + assert titles == ["fred_stuff", "george"] + end + + test "bulk updates honor update action filters when streaming" do + Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + + Post + |> Ash.bulk_update!(:update_only_freds, %{}, + strategy: :stream, + return_errors?: true, + atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")} + ) + + titles = + Post + |> Ash.read!() + |> Enum.map(& &1.title) + |> Enum.sort() + + assert titles == ["fred_stuff", "george"] + end + test "errors in streaming bulk updates that would result in rollbacks are handled" do Ash.bulk_create!( [ diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 2e8c995e..5a963f0b 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -79,6 +79,14 @@ defmodule AshPostgres.Test.Post do defaults([:read, :destroy]) + destroy :destroy_only_freds do + change(filter(expr(title == "fred"))) + end + + update :update_only_freds do + change(filter(expr(title == "fred"))) + end + destroy :destroy_with_confirm do require_atomic?(false) argument(:confirm, :string, allow_nil?: false) From 8a69af9a4bd5bc8acdfac13519ffad25bacc25a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 09:17:56 -0400 Subject: [PATCH 0428/1215] chore(deps-dev): bump git_ops from 2.6.0 to 2.6.1 (#278) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index cabc437b..1512ed2d 100644 --- a/mix.lock +++ b/mix.lock @@ -18,7 +18,7 @@ "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, + "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, From 4b6f2baddd36aa36407108a7df9918978ee15740 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 May 2024 13:26:37 -0400 Subject: [PATCH 0429/1215] docs: fix link to pg docs --- documentation/topics/advanced/expressions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/topics/advanced/expressions.md b/documentation/topics/advanced/expressions.md index 62d4f3f8..3195c58c 100644 --- a/documentation/topics/advanced/expressions.md +++ b/documentation/topics/advanced/expressions.md @@ -30,7 +30,7 @@ fragment("points > (SELECT SUM(points) FROM games WHERE user_id = ? AND id != ?) > > Using entire queries as shown above is a last resort, but can sometimes be the best way to accomplish a given task. -#### In calculations +#### In calculations ```elixir calculations do @@ -52,7 +52,7 @@ end These wrap the postgres builtin like and ilike operators. -Please be aware, these match *patterns* not raw text. Use `contains/1` if you want to match text without supporting patterns, i.e `%` and `_` have semantic meaning! +Please be aware, these match _patterns_ not raw text. Use `contains/1` if you want to match text without supporting patterns, i.e `%` and `_` have semantic meaning! For example: @@ -68,7 +68,7 @@ Ash.Query.filter(User, ilike(name, "%ObO%")) # name contains ObO anywhere in the To use this expression, you must have the `pg_trgm` extension in your repos `installed_extensions` list. -This calls the `similarity` function from that extension. See more https://www.postgresql.org/docs/current/pgtrgm.htmlhere: https://www.postgresql.org/docs/current/pgtrgm.html +This calls the `similarity` function from that extension. See more in the [pgtrgm guide](https://www.postgresql.org/docs/current/pgtrgm.html) For example: From 6d0cca1faec6346bb24f31c026da642d1d6332aa Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 14 May 2024 14:35:40 +0200 Subject: [PATCH 0430/1215] test: show error with calc depending on other exists calc (#280) --- test/calculation_test.exs | 9 +++++++++ test/support/resources/author.ex | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 50bc3d78..feb86733 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -59,6 +59,8 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.filter(c_times_p == 6) |> Ash.read!() + Logger.configure(level: :debug) + assert [] = Post |> Ash.Query.filter(author: [has_posts: true]) @@ -131,6 +133,13 @@ defmodule AshPostgres.CalculationTest do Post |> Ash.Query.load([:has_author, :has_comments]) |> Ash.read!() + + # building on top of an exists also works + author = + author |> Ash.load!([:has_posts, :has_no_posts]) + + assert author.has_posts + refute author.has_no_posts end test "calculations can refer to embedded attributes" do diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 3c2dd82a..170cb98c 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -130,7 +130,8 @@ defmodule AshPostgres.Test.Author do argument(:separator, :string, default: " ", constraints: [allow_empty?: true, trim?: false]) end - calculate(:has_posts, :boolean, expr(exists(posts, true))) + calculate(:has_posts, :boolean, expr(exists(posts, true == true))) + calculate(:has_no_posts, :boolean, expr(has_posts == false)) end aggregates do From 11852aec932d478e868a6e6dffc6492778fa815d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 14 May 2024 12:36:16 -0400 Subject: [PATCH 0431/1215] chore: update deps and add test --- mix.lock | 2 +- test/calculation_test.exs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 1512ed2d..5ed3c929 100644 --- a/mix.lock +++ b/mix.lock @@ -31,7 +31,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.1.0", "9c129fa1bd7290014acf6f73e292f43938c17e3fccd7b7df6f41122cab45dda9", [:mix], [], "hexpm", "b9c348688e2cfc20acfef0feaca88643044be5acd2e0b02cf4a8d6ac1edc4c4a"}, - "spark": {:hex, :spark, "2.1.20", "204db8fd28378783c28a9dcb0bebdaf1d51b14a9ea106e1080457d29510a66ea", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e7a4f8f8ca7a477918af1eb65e20f2015f783a9a23e5f73d1020edf5b2ef69be"}, + "spark": {:hex, :spark, "2.1.21", "0c2e5c24bc99f65ee874a563f9f3ba6e5c7c8a79b7de4b3b65af770ca6c8120e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e8a32fd3138524096553d908f37ddb23f0c7cb731d75018d5f2ad6cb583714ec"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index feb86733..7ed741cf 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -59,8 +59,6 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.filter(c_times_p == 6) |> Ash.read!() - Logger.configure(level: :debug) - assert [] = Post |> Ash.Query.filter(author: [has_posts: true]) From 3371b6a313a23fb32e12a623a926e81fb859830d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 14:13:21 -0400 Subject: [PATCH 0432/1215] chore(deps): bump ash from 3.0.0 to 3.0.1 (#281) --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 5ed3c929..629114e9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.0", "2ef88639fce9f126c57c115f955d7e29919942afe74adc00cb7250fd7ced9f5f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ffed4651c9faf79e90066afdd52202e3f8951624bf73fd8ad34aa4c22fceef4b"}, + "ash": {:hex, :ash, "3.0.1", "c4941a3957f03bdc1dd9d4d4ea7e9af9241231c033ad0f25f6327be44ed9a705", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b2d41ab1fc2e4a525962d588bc38ad327be6eb5d37d49ee16561233f3a76bf2"}, "ash_sql": {:hex, :ash_sql, "0.1.2", "a479348d46bf19ec0c981432a47ae8f15781d559bfc708a0260853acb8cc848b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "35fab32c2e5316fac8552dd541707301c616bf548735af593581dcebc76215eb"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -31,10 +31,10 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.1.0", "9c129fa1bd7290014acf6f73e292f43938c17e3fccd7b7df6f41122cab45dda9", [:mix], [], "hexpm", "b9c348688e2cfc20acfef0feaca88643044be5acd2e0b02cf4a8d6ac1edc4c4a"}, - "spark": {:hex, :spark, "2.1.21", "0c2e5c24bc99f65ee874a563f9f3ba6e5c7c8a79b7de4b3b65af770ca6c8120e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e8a32fd3138524096553d908f37ddb23f0c7cb731d75018d5f2ad6cb583714ec"}, + "spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, + "stream_data": {:hex, :stream_data, "1.0.0", "c1380747a4650902732696861d5cb66ad3cb1cc93f31c2c8498bf87cddbabe2d", [:mix], [], "hexpm", "acd53e27c66c617d466f42ec77a7f59e5751f6051583c621ccdb055b9690435d"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, From 555a737493f084067ad3d1a022ebe44c06ceeff7 Mon Sep 17 00:00:00 2001 From: Sucipto <1310895+suciptoid@users.noreply.github.com> Date: Wed, 15 May 2024 18:22:28 +0700 Subject: [PATCH 0433/1215] fix: remove duplicate repo flags (#285) --- lib/mix/tasks/ash_postgres.rollback.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index 839f2b47..ac5da2fa 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -71,6 +71,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do |> AshPostgres.Mix.Helpers.delete_flag("--tenants") |> AshPostgres.Mix.Helpers.delete_flag("--only-tenants") |> AshPostgres.Mix.Helpers.delete_flag("--except-tenants") + |> AshPostgres.Mix.Helpers.delete_arg("-r") Mix.Task.reenable("ecto.rollback") From e597c39687306da0f42e6a9f62dd504acdc19b97 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 15 May 2024 15:50:36 +0200 Subject: [PATCH 0434/1215] add test for rels in atomic updates (#286) --- test/atomics_test.exs | 20 ++++++++++++++++++++ test/support/resources/post.ex | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 62828fa0..405049a6 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -1,4 +1,5 @@ defmodule AshPostgres.AtomicsTest do + alias AshPostgres.Test.Author use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post @@ -101,4 +102,23 @@ defmodule AshPostgres.AtomicsTest do assert Post.increment_score!(post, 2).score == 4 end + + test "use rel in atomic update" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) + |> Ash.create!() + + post = + post + |> Ash.Changeset.for_update(:set_title_from_author, %{}) + |> Ash.update!() + + assert post.title == "John" + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 5a963f0b..fb36b41a 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -150,6 +150,10 @@ defmodule AshPostgres.Test.Post do ) end + update :set_title_from_author do + change(atomic_update(:title, expr(author.first_name))) + end + update :increment_score do argument(:amount, :integer, default: 1) change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) From 6d825792b3d635b5f847e7a56c39f7a7bfb7688c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 May 2024 11:15:34 -0400 Subject: [PATCH 0435/1215] fix: rework the update and destroy query builder to support multiple kinds of joining --- lib/data_layer.ex | 143 +++++++++++++++++++++++++++--------------- test/atomics_test.exs | 40 +++++++++++- 2 files changed, 133 insertions(+), 50 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a93de5ed..76a851cb 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1310,7 +1310,98 @@ defmodule AshPostgres.DataLayer do end end - defp bulk_updatable_query(query, resource, atomics, calculations, context) do + defp bulk_updatable_query(query, resource, atomics, calculations, context, type \\ :update) do + requires_adding_inner_join? = + case type do + :update -> + # could potentially optimize this to avoid the subquery by shuffling free + # inner joins to the top of the query + has_inner_join_to_start? = + case Enum.at(query.joins, 0) do + nil -> + false + + %{qual: :inner} -> + true + + _ -> + false + end + + cond do + has_inner_join_to_start? -> + false + + Enum.any?(query.joins, &(&1.qual != :inner)) -> + true + + Enum.any?(atomics ++ calculations, fn {_, expr} -> + Ash.Filter.list_refs(expr) |> Enum.any?(&(&1.relationship_path != [])) + end) -> + true + + true -> + false + end + + :destroy -> + Enum.any?(query.joins, &(&1.qual != :inner)) || + Enum.any?(atomics ++ calculations, fn {_, expr} -> + expr |> Ash.Filter.list_refs() |> Enum.any?(&(&1.relationship_path != [])) + end) + end + + needs_to_join? = + requires_adding_inner_join? || + query.limit || query.offset + + query = + if needs_to_join? do + root_query = Ecto.Query.exclude(query, :select) + + root_query = + cond do + query.limit || query.offset -> + from(row in Ecto.Query.subquery(root_query), []) + + !Enum.empty?(query.joins) -> + from(row in Ecto.Query.subquery(Ecto.Query.exclude(root_query, :order_by)), []) + + true -> + Ecto.Query.exclude(root_query, :order_by) + end + + dynamic = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row, joining], + field(row, ^pkey) == field(joining, ^pkey) and ^dynamic + ) + else + Ecto.Query.dynamic([row, joining], field(row, ^pkey) == field(joining, ^pkey)) + end + end) + + faked_query = + from(row in query.from.source, + inner_join: limiter in ^root_query, + as: ^0, + on: ^dynamic + ) + |> AshSql.Bindings.default_bindings( + query.__ash_bindings__.resource, + AshPostgres.SqlImplementation, + context + ) + + faked_query + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by) + end + Enum.reduce_while(atomics ++ calculations, {:ok, query}, fn {_, expr}, {:ok, query} -> used_aggregates = Ash.Filter.used_aggregates(expr, []) @@ -1332,53 +1423,6 @@ defmodule AshPostgres.DataLayer do {:halt, {:error, error}} end end) - |> case do - {:ok, query} -> - needs_to_join? = - Enum.any?(query.joins, &(&1.qual != :inner)) || query.limit || query.offset - - if needs_to_join? do - root_query = Ecto.Query.exclude(query, :select) - - root_query = - if query.limit || query.offset do - Map.put(root_query, :order_bys, query.order_bys) - else - Ecto.Query.exclude(root_query, :order_by) - end - - dynamic = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row, joining], - field(row, ^pkey) == field(joining, ^pkey) and ^dynamic - ) - else - Ecto.Query.dynamic([row, joining], field(row, ^pkey) == field(joining, ^pkey)) - end - end) - - faked_query = - from(row in query.from.source, - inner_join: limiter in ^subquery(root_query), - as: ^0, - on: ^dynamic - ) - |> Map.put(:__ash_bindings__, query.__ash_bindings__) - - {:ok, faked_query} - else - {:ok, - query - |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation, context) - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by)} - end - - {:error, error} -> - {:error, error} - end end @impl true @@ -1399,7 +1443,8 @@ defmodule AshPostgres.DataLayer do resource, changeset.atomics, options[:calculations] || [], - changeset.context + changeset.context, + :destroy ) do {:error, error} -> {:error, error} diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 405049a6..a311f7c6 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -4,6 +4,7 @@ defmodule AshPostgres.AtomicsTest do alias AshPostgres.Test.Post import Ash.Expr + require Ash.Query test "atomics work on upserts" do id = Ash.UUID.generate() @@ -103,7 +104,7 @@ defmodule AshPostgres.AtomicsTest do assert Post.increment_score!(post, 2).score == 4 end - test "use rel in atomic update" do + test "relationships can be used in atomic update" do author = Author |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) @@ -121,4 +122,41 @@ defmodule AshPostgres.AtomicsTest do assert post.title == "John" end + + test "relationships can be used in atomic update and in an atomic update filter" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) + |> Ash.create!() + + post = + Post + |> Ash.Query.filter(author.last_name == "Doe") + |> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true) + |> Map.get(:records) + |> List.first() + + assert post.title == "John" + end + + test "relationships can be used in atomic update and in an atomic update filter when first join is a left join" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) + |> Ash.create!() + + assert [] = + Post + |> Ash.Query.filter(is_nil(author.last_name)) + |> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true) + |> Map.get(:records) + end end From 3f00dcbe8e91142f1ae51e4694463721ceebfca1 Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Thu, 16 May 2024 01:19:29 +1000 Subject: [PATCH 0436/1215] test: add failing test to demonstrate error using ref (#282) Filtering on ref in this manner worked up until Ash 3.0. Now, it gives the following error: 1) test filter with ref (AshPostgres.FilterTest) test/filter_test.exs:1076 ** (FunctionClauseError) no function clause matching in Ash.Filter.check_filterable/2 The following arguments were given to Ash.Filter.check_filterable/2: # 1 AshPostgres.Test.Organization # 2 :id Attempted function clauses (showing 2 out of 2): defp check_filterable(_resource, []) defp check_filterable(resource, [relationship | rest]) code: |> Ash.Query.filter(^ref(:id, [:posts, :comments]) == ^comment.id) stacktrace: (ash 3.0.1) lib/ash/filter/filter.ex:2944: Ash.Filter.check_filterable/2 (ash 3.0.1) lib/ash/filter/filter.ex:2933: anonymous fn/2 in Ash.Filter.validate_filterable_relationship_paths/2 (elixir 1.16.2) lib/enum.ex:4316: Enum.find_value_list/3 (ash 3.0.1) lib/ash/filter/filter.ex:2923: Ash.Filter.validate_refs/3 (ash 3.0.1) lib/ash/filter/filter.ex:3015: Ash.Filter.resolve_call/2 (ash 3.0.1) lib/ash/filter/filter.ex:2489: Ash.Filter.add_expression_part/3 (ash 3.0.1) lib/ash/filter/filter.ex:2427: anonymous fn/3 in Ash.Filter.parse_expression/2 (elixir 1.16.2) lib/enum.ex:4839: Enumerable.List.reduce/3 (elixir 1.16.2) lib/enum.ex:2582: Enum.reduce_while/3 (ash 3.0.1) lib/ash/filter/filter.ex:334: Ash.Filter.parse/3 (ash 3.0.1) lib/ash/query/query.ex:2574: Ash.Query.do_filter/3 test/filter_test.exs:1095: (test) --- test/filter_test.exs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/test/filter_test.exs b/test/filter_test.exs index 4a9c4442..dc9f9f01 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,10 +1,11 @@ defmodule AshPostgres.FilterTest do - alias AshPostgres.Test.Organization use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Post} + + alias AshPostgres.Test.{Author, Comment, Post, Organization} alias AshPostgres.Test.ComplexCalculations.{Channel, ChannelMember} require Ash.Query + import Ash.Expr describe "with no filter applied" do test "with no data" do @@ -1070,4 +1071,29 @@ defmodule AshPostgres.FilterTest do ) |> Ash.read!() end + + test "filter with ref" do + organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "foo"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) + |> Ash.create!() + + comment = + Comment + |> Ash.Changeset.for_create(:create, %{title: "not match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + fetched_org = + Organization + |> Ash.Query.filter(^ref(:id, [:posts, :comments]) == ^comment.id) + |> Ash.read_one!() + + assert fetched_org.id == organization.id + end end From 61530160803c3643890cc03180dabfae019f6a68 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 May 2024 11:23:26 -0400 Subject: [PATCH 0437/1215] chore: fix test for ^ref --- test/filter_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/filter_test.exs b/test/filter_test.exs index dc9f9f01..5ee520d7 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1091,7 +1091,7 @@ defmodule AshPostgres.FilterTest do fetched_org = Organization - |> Ash.Query.filter(^ref(:id, [:posts, :comments]) == ^comment.id) + |> Ash.Query.filter(^ref([:posts, :comments], :id) == ^comment.id) |> Ash.read_one!() assert fetched_org.id == organization.id From 0d2f8f9916fba736c0e64f530a6214c56271a3c3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 May 2024 16:52:03 -0500 Subject: [PATCH 0438/1215] chore: update ash --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 629114e9..81288019 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.1", "c4941a3957f03bdc1dd9d4d4ea7e9af9241231c033ad0f25f6327be44ed9a705", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b2d41ab1fc2e4a525962d588bc38ad327be6eb5d37d49ee16561233f3a76bf2"}, + "ash": {:hex, :ash, "3.0.2", "2b3bec4c53a04ed8d415052cc992b6c76e0e348fa2fae06cdb28d6769709438c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "784b6a3ade4b0c8e5c16cd5792ab080affef8729d48e32c15e4e283c93696104"}, "ash_sql": {:hex, :ash_sql, "0.1.2", "a479348d46bf19ec0c981432a47ae8f15781d559bfc708a0260853acb8cc848b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "35fab32c2e5316fac8552dd541707301c616bf548735af593581dcebc76215eb"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From a3ee4814dca2d9edfd61f9b94465931210c9b565 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 May 2024 16:53:32 -0500 Subject: [PATCH 0439/1215] chore: release version v2.0.2 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d3e9e8..f9c55813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.2](https://github.com/ash-project/ash_postgres/compare/v2.0.1...v2.0.2) (2024-05-15) + + + + +### Bug Fixes: + +* [update_query/destroy_query] rework the update and destroy query builder to support multiple kinds of joining + +* [mix ash_postgres.migrate] remove duplicate repo flags (#285) + +* [Ash.Error.Changes.StaleRecord] ensure filter is included in stale record error messages we return + +* [AshPostgres.MigrationGenerator] properly parse previous version from migration generation + ## [v2.0.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0...v2.0.1) (2024-05-12) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index bdd80ab4..2164798e 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.1" + @version "2.0.2" def project do [ From d5c55f6b10469bb40151ff26b3ca48ad1bd09cd4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 May 2024 00:14:38 -0500 Subject: [PATCH 0440/1215] fix: add and remove custom indexes in tandem properly --- lib/migration_generator/migration_generator.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 424fdac6..11148e76 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1721,9 +1721,10 @@ defmodule AshPostgres.MigrationGenerator do custom_indexes_to_add = Enum.filter(snapshot.custom_indexes, fn index -> - !Enum.find(old_snapshot.custom_indexes, fn old_custom_index -> - indexes_match?(snapshot.table, old_custom_index, index) - end) + (rewrite_all_identities? && !index.all_tenants?) || + !Enum.find(old_snapshot.custom_indexes, fn old_custom_index -> + indexes_match?(snapshot.table, old_custom_index, index) + end) end) |> Enum.map(fn custom_index -> %Operation.AddCustomIndex{ From 74cead8749f7e9d10cbb4042168b03450e28501b Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Thu, 16 May 2024 16:39:47 +0300 Subject: [PATCH 0441/1215] test: add failing test for `changing_attributes` check for create (#288) --- test/ash_postgres_test.exs | 10 +++++++++- test/support/resources/post.ex | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 921f50d7..b5e2a58f 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -12,7 +12,15 @@ defmodule AshPostgresTest do } end - test "filter policies are applied" do + test "filter policies are applied in create" do + assert_raise Ash.Error.Forbidden, fn -> + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "worst"}) + |> Ash.create!() + end + end + + test "filter policies are applied in update" do post = AshPostgres.Test.Post |> Ash.Changeset.for_create(:create, %{title: "good"}) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index fb36b41a..65ff4d52 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -41,6 +41,10 @@ defmodule AshPostgres.Test.Post do authorize_if(relates_to_actor_via([:author, :authors_with_same_first_name])) authorize_unless(changing_attributes(title: [from: "good", to: "bad"])) end + + policy action(:create) do + authorize_unless(changing_attributes(title: [to: "worst"])) + end end field_policies do From 9b3e5d38fbf32e6cef9ed4fde590564160e563fa Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 16 May 2024 09:53:19 -0500 Subject: [PATCH 0442/1215] test: update test to run authorization --- test/ash_postgres_test.exs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index b5e2a58f..346037ad 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -15,7 +15,7 @@ defmodule AshPostgresTest do test "filter policies are applied in create" do assert_raise Ash.Error.Forbidden, fn -> AshPostgres.Test.Post - |> Ash.Changeset.for_create(:create, %{title: "worst"}) + |> Ash.Changeset.for_create(:create, %{title: "worst"}, authorize?: true) |> Ash.create!() end end @@ -39,10 +39,5 @@ defmodule AshPostgresTest do ) |> Map.get(:title) end - - # post - # |> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true) - # |> Ash.update!() - # |> Map.get(:title) end end From e29aa86870ad6ccc7bc9ee431a35c0a9172d6841 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Thu, 16 May 2024 22:48:38 +0200 Subject: [PATCH 0443/1215] chore: fix bulk destroy tests (#290) * chore: fix bulk destroy tests They were using an :update action * chore: fix credo error --- test/bulk_destroy_test.exs | 6 +++--- test/filter_test.exs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index b04ea246..dafc2926 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -6,13 +6,13 @@ defmodule AshPostgres.BulkDestroyTest do require Ash.Query test "bulk destroys can run with nothing in the table" do - Ash.bulk_destroy!(Post, :update, %{title: "new_title"}) + Ash.bulk_destroy!(Post, :destroy, %{}) end test "bulk destroys destroy everything pertaining to the query" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Ash.bulk_destroy!(Post, :update, %{}) + Ash.bulk_destroy!(Post, :destroy, %{}) assert Ash.read!(Post) == [] end @@ -54,7 +54,7 @@ defmodule AshPostgres.BulkDestroyTest do Post |> Ash.Query.filter(author.first_name == "fred" or title == "fred") |> Ash.Query.select([:title]) - |> Ash.bulk_destroy!(:update, %{}, return_records?: true) + |> Ash.bulk_destroy!(:destroy, %{}, return_records?: true) assert [%{title: "george"}] = Ash.read!(Post) end diff --git a/test/filter_test.exs b/test/filter_test.exs index 5ee520d7..6bdc5edb 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.FilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Post, Organization} + alias AshPostgres.Test.{Author, Comment, Organization, Post} alias AshPostgres.Test.ComplexCalculations.{Channel, ChannelMember} require Ash.Query From ad0b1a5c6c9e0fddd957430fb5c30dad776b723e Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Fri, 17 May 2024 00:28:43 +0200 Subject: [PATCH 0444/1215] chore: add relationship pagination tests (#265) --- .../post_followers/20240516205244.json | 75 ++++ .../20240516205244_migrate_resources23.exs | 30 ++ test/load_test.exs | 328 +++++++++++++++++- test/support/resources/post.ex | 8 + test/support/resources/post_follower.ex | 6 +- test/support/resources/user.ex | 15 + 6 files changed, 457 insertions(+), 5 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240516205244.json create mode 100644 priv/test_repo/migrations/20240516205244_migrate_resources23.exs diff --git a/priv/resource_snapshots/test_repo/post_followers/20240516205244.json b/priv/resource_snapshots/test_repo/post_followers/20240516205244.json new file mode 100644 index 00000000..e4007740 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240516205244.json @@ -0,0 +1,75 @@ +{ + "attributes": [ + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "post_followers_post_id_fkey", + "table": "posts", + "primary_key?": true, + "schema": "public", + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "on_update": null, + "on_delete": null, + "match_with": null, + "match_type": null, + "deferrable": false, + "destination_attribute_generated": null, + "destination_attribute_default": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "follower_id", + "references": { + "name": "post_followers_follower_id_fkey", + "table": "users", + "primary_key?": true, + "schema": "public", + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "on_update": null, + "on_delete": null, + "match_with": null, + "match_type": null, + "deferrable": false, + "destination_attribute_generated": null, + "destination_attribute_default": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + } + ], + "table": "post_followers", + "hash": "B24262368E31327DD90234615B1CA9D569FFCA39CDE74EA434D568AD319D13E3", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "identities": [], + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240516205244_migrate_resources23.exs b/priv/test_repo/migrations/20240516205244_migrate_resources23.exs new file mode 100644 index 00000000..1d0eec51 --- /dev/null +++ b/priv/test_repo/migrations/20240516205244_migrate_resources23.exs @@ -0,0 +1,30 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources23 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 + drop(constraint("post_followers", "post_followers_pkey")) + + alter table(:post_followers) do + modify(:follower_id, :uuid, primary_key: true) + modify(:post_id, :uuid, primary_key: true) + + remove(:id) + end + end + + def down do + drop(constraint("post_followers", "post_followers_pkey")) + + alter table(:post_followers) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + modify(:post_id, :uuid, primary_key: false) + modify(:follower_id, :uuid, primary_key: false) + end + end +end diff --git a/test/load_test.exs b/test/load_test.exs index 079a2141..7b30ea3a 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Comment, Post, Record, TempEntity} + alias AshPostgres.Test.{Author, Comment, Post, Record, TempEntity, User} require Ash.Query @@ -346,4 +346,330 @@ defmodule AshPostgres.Test.LoadTest do assert temp_entity.id == entity.id end end + + describe "relationship pagination" do + test "it allows paginating has_many relationships with offset pagination" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "b"}) + |> Ash.create!() + + for i <- 0..9 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id}) + |> Ash.create!() + end + + paginated_posts = + Post + |> Ash.Query.for_read(:paginated) + |> Ash.Query.page(limit: 2, offset: 2) + |> Ash.Query.sort(:title) + + assert [author1, author2] = + Author + |> Ash.Query.sort(:first_name) + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!() + + assert %Ash.Page.Offset{ + results: [%{title: "author1 post2"}, %{title: "author1 post3"}] + } = author1.posts + + assert %Ash.Page.Offset{ + results: [%{title: "author2 post2"}, %{title: "author2 post3"}] + } = author2.posts + + assert %Ash.Page.Offset{ + results: [%{title: "author1 post4"}, %{title: "author1 post5"}] + } = Ash.page!(author1.posts, :next) + end + + test "it allows paginating has_many relationships with keyset pagination" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "b"}) + |> Ash.create!() + + for i <- 0..9 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id}) + |> Ash.create!() + end + + paginated_posts = + Post + |> Ash.Query.for_read(:keyset) + |> Ash.Query.page(limit: 2) + |> Ash.Query.sort(:title) + + assert [author1, author2] = + Author + |> Ash.Query.sort(:first_name) + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!() + + assert %Ash.Page.Keyset{ + results: [%{title: "author1 post0"}, %{title: "author1 post1"}] + } = author1.posts + + assert %Ash.Page.Keyset{ + results: [%{title: "author2 post0"}, %{title: "author2 post1"}] + } = author2.posts + + assert %Ash.Page.Keyset{ + results: [%{title: "author1 post2"}, %{title: "author1 post3"}] + } = Ash.page!(author1.posts, :next) + end + + test "it allows paginating many_to_many relationships with offset pagination" do + followers = + for i <- 0..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers_0_to_6 = Enum.take(followers, 6) + followers_5_to_9 = Enum.slice(followers, 5..9) + + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.Changeset.manage_relationship(:followers, followers_0_to_6, type: :append_and_remove) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "b"}) + |> Ash.Changeset.manage_relationship(:followers, followers_5_to_9, type: :append_and_remove) + |> Ash.create!() + + paginated_followers = + User + |> Ash.Query.page(limit: 2) + |> Ash.Query.sort(:name) + + assert [post1, post2] = + Post + |> Ash.Query.sort(:title) + |> Ash.Query.load(followers: paginated_followers) + |> Ash.read!() + + assert %Ash.Page.Offset{ + results: [%{name: "user0"}, %{name: "user1"}] + } = post1.followers + + assert %Ash.Page.Offset{ + results: [%{name: "user5"}, %{name: "user6"}] + } = post2.followers + + assert %Ash.Page.Offset{ + results: [%{name: "user2"}, %{name: "user3"}] + } = Ash.page!(post1.followers, :next) + end + + test "it allows paginating many_to_many relationships with keyset pagination" do + followers = + for i <- 0..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}"}) + |> Ash.create!() + end + + followers_0_to_6 = Enum.take(followers, 6) + followers_5_to_9 = Enum.slice(followers, 5..9) + + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.Changeset.manage_relationship(:followers, followers_0_to_6, type: :append_and_remove) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "b"}) + |> Ash.Changeset.manage_relationship(:followers, followers_5_to_9, type: :append_and_remove) + |> Ash.create!() + + paginated_followers = + User + |> Ash.Query.for_read(:keyset) + |> Ash.Query.page(limit: 2) + |> Ash.Query.sort(:name) + + assert [post1, post2] = + Post + |> Ash.Query.sort(:title) + |> Ash.Query.load(followers: paginated_followers) + |> Ash.read!() + + assert %Ash.Page.Keyset{ + results: [%{name: "user0"}, %{name: "user1"}] + } = post1.followers + + assert %Ash.Page.Keyset{ + results: [%{name: "user5"}, %{name: "user6"}] + } = post2.followers + + assert %Ash.Page.Keyset{ + results: [%{name: "user2"}, %{name: "user3"}] + } = Ash.page!(post1.followers, :next) + end + + test "works when nested with offset" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "b"}) + |> Ash.create!() + + followers = + for i <- 0..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers_0_to_6 = Enum.take(followers, 6) + followers_5_to_9 = Enum.slice(followers, 5..9) + + for i <- 0..5 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id}) + |> Ash.Changeset.manage_relationship(:followers, followers_0_to_6, + type: :append_and_remove + ) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id}) + |> Ash.Changeset.manage_relationship(:followers, followers_5_to_9, + type: :append_and_remove + ) + |> Ash.create!() + end + + paginated_followers = + User + |> Ash.Query.page(limit: 1) + |> Ash.Query.sort(:name) + + paginated_posts = + Post + |> Ash.Query.for_read(:paginated) + |> Ash.Query.load(followers: paginated_followers) + |> Ash.Query.page(limit: 1) + |> Ash.Query.sort(:title) + + assert %Ash.Page.Offset{results: [author1]} = + Author + |> Ash.Query.sort(:first_name) + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!(page: [limit: 1]) + + assert %Ash.Page.Offset{ + results: [ + %{ + title: "author1 post0", + followers: %Ash.Page.Offset{results: [%{name: "user0"}]} = followers_page + } + ] + } = author1.posts + + assert %Ash.Page.Offset{results: [%{title: "author1 post1"}]} = + Ash.page!(author1.posts, :next) + + assert %Ash.Page.Offset{results: [%{name: "user1"}]} = Ash.page!(followers_page, :next) + end + + test "works when nested with keyset" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "b"}) + |> Ash.create!() + + followers = + for i <- 0..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers_0_to_6 = Enum.take(followers, 6) + followers_5_to_9 = Enum.slice(followers, 5..9) + + for i <- 0..5 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id}) + |> Ash.Changeset.manage_relationship(:followers, followers_0_to_6, + type: :append_and_remove + ) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id}) + |> Ash.Changeset.manage_relationship(:followers, followers_5_to_9, + type: :append_and_remove + ) + |> Ash.create!() + end + + paginated_followers = + User + |> Ash.Query.for_read(:keyset) + |> Ash.Query.page(limit: 1) + |> Ash.Query.sort(:name) + + paginated_posts = + Post + |> Ash.Query.for_read(:keyset) + |> Ash.Query.load(followers: paginated_followers) + |> Ash.Query.page(limit: 1) + |> Ash.Query.sort(:title) + + assert [author1, _author2] = + Author + |> Ash.Query.sort(:first_name) + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!() + + assert %Ash.Page.Keyset{ + results: [ + %{ + title: "author1 post0", + followers: %Ash.Page.Keyset{results: [%{name: "user0"}]} = followers_page + } + ] + } = author1.posts + + assert %Ash.Page.Keyset{results: [%{title: "author1 post1"}]} = + Ash.page!(author1.posts, :next) + + assert %Ash.Page.Keyset{results: [%{name: "user1"}]} = Ash.page!(followers_page, :next) + end + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 65ff4d52..1c554a98 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -135,6 +135,14 @@ defmodule AshPostgres.Test.Post do filter(expr(title == "foo")) end + read :keyset do + pagination do + keyset?(true) + countable(true) + required?(false) + end + end + read(:allow_any) read :paginated do diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex index e60144bf..2da212d9 100644 --- a/test/support/resources/post_follower.ex +++ b/test/support/resources/post_follower.ex @@ -15,17 +15,15 @@ defmodule AshPostgres.Test.PostFollower do defaults([:create, :read, :update, :destroy]) end - attributes do - uuid_primary_key(:id) - end - relationships do belongs_to :post, AshPostgres.Test.Post do + primary_key?(true) public?(true) allow_nil?(false) end belongs_to :follower, AshPostgres.Test.User do + primary_key?(true) public?(true) allow_nil?(false) end diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index f416eb98..bc57fe4f 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -11,6 +11,21 @@ defmodule AshPostgres.Test.User do read :active do filter(expr(active)) + + pagination do + offset?(true) + keyset?(true) + countable(true) + required?(false) + end + end + + read :keyset do + pagination do + keyset?(true) + countable(true) + required?(false) + end end end From 814e7de496ab17aa061d5df34324dde68082fe21 Mon Sep 17 00:00:00 2001 From: Davide Briani <60540759+davidebriani@users.noreply.github.com> Date: Fri, 17 May 2024 02:18:17 +0200 Subject: [PATCH 0445/1215] improvement: support `on_delete: :nilify` for specific columns (#289) --- .../dsls/DSL:-AshPostgres.DataLayer.md | 4 +- documentation/topics/resources/references.md | 36 +++++++-- lib/data_layer.ex | 14 +++- .../migration_generator.ex | 30 +++++++- lib/migration_generator/operation.ex | 4 + lib/reference.ex | 7 +- test/migration_generator_test.exs | 75 +++++++++++++++++++ 7 files changed, 156 insertions(+), 14 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 8dd07ecf..5a73ef90 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -258,7 +258,7 @@ end | Name | Type | Default | Docs | |------|------|---------|------| -| [`polymorphic_on_delete`](#postgres-references-polymorphic_on_delete){: #postgres-references-polymorphic_on_delete } | `:delete \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | +| [`polymorphic_on_delete`](#postgres-references-polymorphic_on_delete){: #postgres-references-polymorphic_on_delete } | `:delete \| :nilify \| {:nilify, columns} \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | | [`polymorphic_on_update`](#postgres-references-polymorphic_on_update){: #postgres-references-polymorphic_on_update } | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | @@ -297,7 +297,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post | Name | Type | Default | Docs | |------|------|---------|------| | [`ignore?`](#postgres-references-reference-ignore?){: #postgres-references-reference-ignore? } | `boolean` | | If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way | -| [`on_delete`](#postgres-references-reference-on_delete){: #postgres-references-reference-on_delete } | `:delete \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | +| [`on_delete`](#postgres-references-reference-on_delete){: #postgres-references-reference-on_delete } | `:delete \| :nilify \| {:nilify, columns} \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | | [`on_update`](#postgres-references-reference-on_update){: #postgres-references-reference-on_update } | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | | [`deferrable`](#postgres-references-reference-deferrable){: #postgres-references-reference-deferrable } | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | | [`name`](#postgres-references-reference-name){: #postgres-references-reference-name } | `String.t` | | The name of the foreign key to generate in the database. Defaults to
__fkey | diff --git a/documentation/topics/resources/references.md b/documentation/topics/resources/references.md index 1922e480..91b9996c 100644 --- a/documentation/topics/resources/references.md +++ b/documentation/topics/resources/references.md @@ -14,6 +14,36 @@ end > > No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens _directly_ in the database. +## On Delete + +This option describes what to do if the referenced row is deleted. + +The option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, _not_ a `destroy` action in your resource. See the warning above. + +The possible values for the option are `:nothing`, `:restrict`, `:delete`, `:nilify`, `{:nilify, columns}`. + +With `:nothing` or `:restrict` the deletion of the referenced row is prevented. + +With `:delete` the row is deleted together with the referenced row. + +With `:nilify` all columns of the foreign-key constraint are nilified. + +With `{:nilify, columns}` a column list can specify which columns should be set to `nil`. +If you intend to use this option to nilify a subset of the columns, note that it cannot be used together with the `match: :full` option otherwise a mix of nil and non-nil values would fail the constraint and prevent the deletion of the referenced row. +In addition, keep into consideration that this option is only supported from Postgres v15.0 onwards. + +## On Update + +This option describes what to do if the referenced row is updated. + +The possible values for the option are `:nothing`, `:restrict`, `:update`, `:nilify`. + +With `:nothing` or `:restrict` the update of the referenced row is prevented. + +With `:update` the row is updated according to the referenced row. + +With `:nilify` all columns of the foreign-key constraint are nilified. + ## Nothing vs Restrict ```elixir @@ -24,8 +54,4 @@ references do end ``` -The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). `:restrict` will prevent the deletion from happening _before_ the end of the database transaction, whereas `:nothing` allows the transaction to complete before doing so. This allows for things like updating or deleting the destination row and _then_ updating updating or deleting the reference(as long as you are in a transaction). The reason that `:nothing` still ultimately prevents deletion is because postgres enforces foreign key referential integrity. - -## On Delete - -This option is called `on_delete`, instead of `on_destroy`, because it is hooking into the database level deletion, _not_ a `destroy` action in your resource. See the warning above. +The difference between `:nothing` and `:restrict` is subtle and, if you are unsure, choose `:nothing` (the default behavior). `:restrict` will immediately check the foreign-key constraint and prevent the update or deletion from happening, whereas `:nothing` allows the check to be deferred until later in the transaction. This allows for things like updating or deleting the destination row and _then_ updating updating or deleting the reference (as long as you are in a transaction). The reason that `:nothing` still ultimately prevents the update or deletion is because postgres enforces foreign key referential integrity. diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 76a851cb..1c9f7ec5 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -154,7 +154,12 @@ defmodule AshPostgres.DataLayer do entities: [@reference], schema: [ polymorphic_on_delete: [ - type: {:one_of, [:delete, :nilify, :nothing, :restrict]}, + type: + {:or, + [ + {:one_of, [:delete, :nilify, :nothing, :restrict]}, + {:tagged_tuple, :nilify, {:wrap_list, :atom}} + ]}, doc: "For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables." ], @@ -227,7 +232,12 @@ defmodule AshPostgres.DataLayer do entities: [@reference], schema: [ polymorphic_on_delete: [ - type: {:one_of, [:delete, :nilify, :nothing, :restrict]}, + type: + {:or, + [ + {:one_of, [:delete, :nilify, :nothing, :restrict]}, + {:tagged_tuple, :nilify, {:wrap_list, :atom}} + ]}, doc: "For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables." ], diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 11148e76..e00041c3 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2920,9 +2920,7 @@ defmodule AshPostgres.MigrationGenerator do defp snapshot_to_binary(snapshot) do snapshot |> Map.update!(:attributes, fn attributes -> - Enum.map(attributes, fn attribute -> - %{attribute | type: sanitize_type(attribute.type, attribute[:size])} - end) + Enum.map(attributes, &attribute_to_binary/1) end) |> Map.update!(:custom_indexes, fn indexes -> Enum.map(indexes, fn index -> @@ -2938,6 +2936,22 @@ defmodule AshPostgres.MigrationGenerator do |> Jason.encode!(pretty: true) end + defp attribute_to_binary(attribute) do + attribute + |> Map.update!(:references, fn + nil -> + nil + + references -> + references + |> Map.update!(:on_delete, &(&1 && references_on_delete_to_binary(&1))) + end) + |> Map.update!(:type, fn type -> sanitize_type(type, attribute[:size]) end) + end + + defp references_on_delete_to_binary(value) when is_atom(value), do: value + defp references_on_delete_to_binary({:nilify, columns}), do: [:nilify, columns] + defp sanitize_type({:array, type}, size) do ["array", sanitize_type(type, size)] end @@ -3094,7 +3108,7 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put_new(:destination_attribute_generated, false) |> Map.put_new(:on_delete, nil) |> Map.put_new(:on_update, nil) - |> Map.update!(:on_delete, &(&1 && maybe_to_atom(&1))) + |> Map.update!(:on_delete, &(&1 && load_references_on_delete(&1))) |> Map.update!(:on_update, &(&1 && maybe_to_atom(&1))) |> Map.put_new(:match_with, nil) |> Map.put_new(:match_type, nil) @@ -3178,6 +3192,14 @@ defmodule AshPostgres.MigrationGenerator do Map.put_new(index, :index_name, "#{table}_#{name}_unique_index") end + defp load_references_on_delete(["nilify", columns]) when is_list(columns) do + {:nilify, Enum.map(columns, &maybe_to_atom/1)} + end + + defp load_references_on_delete(value) do + maybe_to_atom(value) + end + defp maybe_to_atom(value) when is_atom(value), do: value defp maybe_to_atom(value), do: String.to_atom(value) end diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 97092c40..55005251 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -42,6 +42,10 @@ defmodule AshPostgres.MigrationGenerator.Operation do end end + def on_delete(%{on_delete: {:nilify, columns}}) when is_list(columns) do + "on_delete: {:nilify, #{inspect(columns)}}" + end + def on_delete(%{on_delete: on_delete}) when on_delete in [:delete, :nilify] do "on_delete: :#{on_delete}_all" end diff --git a/lib/reference.ex b/lib/reference.ex index d438cf7e..ab980036 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -24,7 +24,12 @@ defmodule AshPostgres.Reference do "If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way" ], on_delete: [ - type: {:one_of, [:delete, :nilify, :nothing, :restrict]}, + type: + {:or, + [ + {:one_of, [:delete, :nilify, :nothing, :restrict]}, + {:tagged_tuple, :nilify, {:wrap_list, :atom}} + ]}, doc: """ What should happen to records of this resource when the referenced record of the *destination* resource is deleted. """ diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 2446216e..11b6758f 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1246,6 +1246,81 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(file) =~ ~S[references(:users, column: :id, name: "user_things2_user_id_fkey", type: :uuid, prefix: "public")] end + + test "references on_delete: {:nilify, columns} works with multitenant resources" do + defresource Tenant, "tenants" do + attributes do + uuid_primary_key(:id) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + end + + defresource Group, "groups" do + attributes do + uuid_primary_key(:id) + end + + multitenancy do + strategy(:attribute) + attribute(:tenant_id) + end + + relationships do + belongs_to(:tenant, Tenant) + end + + postgres do + references do + reference(:tenant, on_delete: :delete) + end + end + end + + defresource Item, "items" do + attributes do + uuid_primary_key(:id) + end + + multitenancy do + strategy(:attribute) + attribute(:tenant_id) + end + + relationships do + belongs_to(:group, Group) + belongs_to(:tenant, Tenant) + end + + postgres do + references do + reference(:group, + match_with: [tenant_id: :tenant_id], + on_delete: {:nilify, [:group_id]} + ) + + reference(:tenant, on_delete: :delete) + end + end + end + + defdomain([Tenant, Group, Item]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ + ~S + end end describe "check constraints" do From eac2c722ea84ab9c7ddeb1a9c623889fc4afb76c Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Fri, 17 May 2024 19:31:45 +0800 Subject: [PATCH 0446/1215] docs: Clarify that `references` go inside a `postgres` block (#291) --- documentation/topics/resources/references.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/documentation/topics/resources/references.md b/documentation/topics/resources/references.md index 91b9996c..91328ddc 100644 --- a/documentation/topics/resources/references.md +++ b/documentation/topics/resources/references.md @@ -1,12 +1,16 @@ # References -To configure the behavior of generated foreign keys on a resource, we use the `references` section. +To configure the behavior of generated foreign keys on a resource, we use the `references` section, within the `postgres` configuration block. For example: ```elixir -references do - reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" +postgres do + # other PostgreSQL config here + + references do + reference :post, on_delete: :delete, on_update: :update, name: "comments_to_posts_fkey" + end end ``` From 06f0f9befe5432bbf368bc83a97d213653d1638d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 09:41:43 -0400 Subject: [PATCH 0447/1215] chore(deps): bump ecto_sql from 3.11.1 to 3.11.2 (#293) Bumps [ecto_sql](https://github.com/elixir-ecto/ecto_sql) from 3.11.1 to 3.11.2. - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto_sql/compare/v3.11.1...v3.11.2) --- updated-dependencies: - dependency-name: ecto_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 81288019..7a3b0d0e 100644 --- a/mix.lock +++ b/mix.lock @@ -11,7 +11,7 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.2", "c7cc7f812af571e50b80294dc2e535821b3b795ce8008d07aa5f336591a185a8", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "73c07f995ac17dbf89d3cfaaf688fcefabcd18b7b004ac63b0dc4ef39499ed6b"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, @@ -26,7 +26,7 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, + "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.2", "b2be82b1c3402537d06a8f85bb1849f72cb6b4be140495cb8956de7aec2fdebd", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c35eb23b77cc77ba922af108722ac93257899e35cfdd18882f0e659ad2cac9f3"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, From 0313734db0a151fa03d882d5be16126807b6ac94 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Mon, 20 May 2024 16:06:49 +0200 Subject: [PATCH 0448/1215] chore: failing test for calculation ordered paginated relationship (#292) --- .../post_followers/20240517223946.json | 85 ++++++++++++++ .../20240517223946_migrate_resources24.exs | 21 ++++ test/load_test.exs | 111 ++++++++++++++++++ test/support/resources/post.ex | 13 ++ test/support/resources/post_follower.ex | 4 + 5 files changed, 234 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240517223946.json create mode 100644 priv/test_repo/migrations/20240517223946_migrate_resources24.exs diff --git a/priv/resource_snapshots/test_repo/post_followers/20240517223946.json b/priv/resource_snapshots/test_repo/post_followers/20240517223946.json new file mode 100644 index 00000000..5ae0c198 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240517223946.json @@ -0,0 +1,85 @@ +{ + "attributes": [ + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "order", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "post_followers_post_id_fkey", + "table": "posts", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "follower_id", + "references": { + "name": "post_followers_follower_id_fkey", + "table": "users", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + } + ], + "table": "post_followers", + "hash": "90A18FEF426DFEB27D87699FD791652300BCB4DE52BC43FBE71A571A596723CF", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240517223946_migrate_resources24.exs b/priv/test_repo/migrations/20240517223946_migrate_resources24.exs new file mode 100644 index 00000000..22998699 --- /dev/null +++ b/priv/test_repo/migrations/20240517223946_migrate_resources24.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources24 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(:post_followers) do + add(:order, :bigint) + end + end + + def down do + alter table(:post_followers) do + remove(:order) + end + end +end diff --git a/test/load_test.exs b/test/load_test.exs index 7b30ea3a..aa3c9c14 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -531,6 +531,117 @@ defmodule AshPostgres.Test.LoadTest do } = Ash.page!(post1.followers, :next) end + test "it allows paginating calculation ordered many_to_many relationships with offset" do + followers = + for i <- 0..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers_0_to_6_reversed = + Enum.take(followers, 7) + |> Enum.with_index() + |> Enum.map(fn {follower, idx} -> %{id: follower.id, order: 6 - idx} end) + + followers_5_to_9_reversed = + Enum.slice(followers, 5..9) + |> Enum.with_index() + |> Enum.map(fn {follower, idx} -> %{id: follower.id, order: 9 - idx} end) + + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.Changeset.manage_relationship(:followers, followers_0_to_6_reversed, + on_lookup: {:relate_and_update, :create, :read, [:order]} + ) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "b"}) + |> Ash.Changeset.manage_relationship(:followers, followers_5_to_9_reversed, + on_lookup: {:relate_and_update, :create, :read, [:order]} + ) + |> Ash.create!() + + paginated_sorted_followers = + User + |> Ash.Query.page(limit: 2) + + assert [post1, post2] = + Post + |> Ash.Query.sort(:title) + |> Ash.Query.load(sorted_followers: paginated_sorted_followers) + |> Ash.read!() + + assert %Ash.Page.Offset{ + results: [%{name: "user6"}, %{name: "user5"}] + } = post1.sorted_followers + + assert %Ash.Page.Offset{ + results: [%{name: "user9"}, %{name: "user8"}] + } = post2.sorted_followers + + assert %Ash.Page.Offset{ + results: [%{name: "user4"}, %{name: "user3"}] + } = Ash.page!(post1.sorted_followers, :next) + end + + test "it allows paginating calculation ordered many_to_many relationships with keyset" do + followers = + for i <- 0..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers_0_to_6_reversed = + Enum.take(followers, 7) + |> Enum.with_index() + |> Enum.map(fn {follower, idx} -> %{id: follower.id, order: 6 - idx} end) + + followers_5_to_9_reversed = + Enum.slice(followers, 5..9) + |> Enum.with_index() + |> Enum.map(fn {follower, idx} -> %{id: follower.id, order: 9 - idx} end) + + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.Changeset.manage_relationship(:followers, followers_0_to_6_reversed, + on_lookup: {:relate_and_update, :create, :read, [:order]} + ) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "b"}) + |> Ash.Changeset.manage_relationship(:followers, followers_5_to_9_reversed, + on_lookup: {:relate_and_update, :create, :read, [:order]} + ) + |> Ash.create!() + + paginated_sorted_followers = + User + |> Ash.Query.for_read(:keyset) + |> Ash.Query.page(limit: 2) + + assert [post1, post2] = + Post + |> Ash.Query.sort(:title) + |> Ash.Query.load(sorted_followers: paginated_sorted_followers) + |> Ash.read!() + + assert %Ash.Page.Keyset{ + results: [%{name: "user6"}, %{name: "user5"}] + } = post1.sorted_followers + + assert %Ash.Page.Keyset{ + results: [%{name: "user9"}, %{name: "user8"}] + } = post2.sorted_followers + + assert %Ash.Page.Keyset{ + results: [%{name: "user4"}, %{name: "user3"}] + } = Ash.page!(post1.sorted_followers, :next) + end + test "works when nested with offset" do author1 = Author diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 1c554a98..8ee5f0d6 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -22,6 +22,8 @@ defmodule AshPostgres.Test.Post do Ash.Policy.Authorizer ] + require Ash.Sort + policies do bypass action_type(:read) do # Check that the post is in the same org as actor @@ -312,6 +314,17 @@ defmodule AshPostgres.Test.Post do read_action: :active ) + has_many(:post_followers, AshPostgres.Test.PostFollower) + + many_to_many(:sorted_followers, AshPostgres.Test.User, + public?: true, + through: AshPostgres.Test.PostFollower, + join_relationship: :post_followers, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :follower_id, + sort: [Ash.Sort.expr_sort(parent(post_followers.order))] + ) + has_many(:views, AshPostgres.Test.PostView) do public?(true) end diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex index 2da212d9..c00ef226 100644 --- a/test/support/resources/post_follower.ex +++ b/test/support/resources/post_follower.ex @@ -15,6 +15,10 @@ defmodule AshPostgres.Test.PostFollower do defaults([:create, :read, :update, :destroy]) end + attributes do + attribute(:order, :integer, public?: true) + end + relationships do belongs_to :post, AshPostgres.Test.Post do primary_key?(true) From 45d0284aa8a0aa9155ba43dce156617010c3368e Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Tue, 21 May 2024 12:42:44 +1000 Subject: [PATCH 0449/1215] test: add failing test to demonstrate an error with nested aggregates (#295) 1) test complex aggregates (AshPostgres.Test.ComplexCalculationsTest) test/complex_calculations_test.exs:66 ** (Ash.Error.Unknown) Unknown Error * ** (Ecto.SubQueryError) the following exception happened when compiling a subquery. ** (Ecto.SubQueryError) the following exception happened when compiling a subquery. ** (Ecto.QueryError) could not find named binding `parent_as(false)` in query: from c0 in AshPostgres.Test.ComplexCalculations.Certification, as: 0, left_lateral_join: s1 in subquery(from s0 in AshPostgres.Test.ComplexCalculations.Skill, as: 0, left_lateral_join: d1 in subquery(from d0 in AshPostgres.Test.ComplexCalculations.Documentation, as: 0, where: parent_as(false).id == as(0).skill_id, where: type( as(0).status, {:parameterized, Ash.Type.Atom.EctoType, one_of: [:demonstrated, :performed, :approved, :reopened]} ) == type( ^"demonstrated", {:parameterized, Ash.Type.Atom.EctoType, one_of: [:demonstrated, :performed, :approved, :reopened]} ), group_by: [d0.skill_id], select: %{ skill_id: map(d0, [:skill_id]).skill_id, count_of_demonstrated_documentations: type( coalesce( count(type(as(0).id, {:parameterized, Ash.Type.UUID.EctoType, []})), type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), on: true, where: parent_as(0).id == as(0).certification_id, where: type(as(0).removed, {:parameterized, Ash.Type.Boolean.EctoType, []}) == type(^false, {:parameterized, Ash.Type.Boolean.EctoType, []}), group_by: [s0.certification_id], select: %{ certification_id: map(s0, [:certification_id]).certification_id, count_of_skills_ever_demonstrated: type( sum( type( fragment( "(CASE WHEN ? THEN ? ELSE ? END)", type( coalesce( as(1).count_of_demonstrated_documentations, type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) == type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}), type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}), type(^1, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), on: true, where: type(as(0).id, {:parameterized, Ash.Type.UUID.EctoType, []}) == type(^"e24ef8a5-d39d-4293-9f69-1abd10a996e6", {:parameterized, Ash.Type.UUID.EctoType, []}), select: merge( merge(struct(c0, [:id]), %{ count_of_skills_ever_demonstrated: type( type( s1.count_of_skills_ever_demonstrated, {:parameterized, Ash.Type.Integer.EctoType, []} ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), %{} ) The subquery originated from the following query: from s0 in AshPostgres.Test.ComplexCalculations.Skill, as: 0, left_lateral_join: d1 in subquery(from d0 in AshPostgres.Test.ComplexCalculations.Documentation, as: 0, where: parent_as(false).id == as(0).skill_id, where: type( as(0).status, {:parameterized, Ash.Type.Atom.EctoType, one_of: [:demonstrated, :performed, :approved, :reopened]} ) == type( ^"demonstrated", {:parameterized, Ash.Type.Atom.EctoType, one_of: [:demonstrated, :performed, :approved, :reopened]} ), group_by: [d0.skill_id], select: %{ skill_id: map(d0, [:skill_id]).skill_id, count_of_demonstrated_documentations: type( coalesce( count(type(as(0).id, {:parameterized, Ash.Type.UUID.EctoType, []})), type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), on: true, where: parent_as(0).id == as(0).certification_id, where: type(as(0).removed, {:parameterized, Ash.Type.Boolean.EctoType, []}) == type(^false, {:parameterized, Ash.Type.Boolean.EctoType, []}), group_by: [s0.certification_id], select: %{ certification_id: map(s0, [:certification_id]).certification_id, count_of_skills_ever_demonstrated: type( sum( type( fragment( "(CASE WHEN ? THEN ? ELSE ? END)", type( coalesce( d1.count_of_demonstrated_documentations, type(^..., {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) == type(^..., {:parameterized, Ash.Type.Integer.EctoType, []}), type(^..., {:parameterized, Ash.Type.Integer.EctoType, []}), type(^..., {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) } The subquery originated from the following query: from c0 in AshPostgres.Test.ComplexCalculations.Certification, as: 0, left_lateral_join: s1 in subquery(from s0 in AshPostgres.Test.ComplexCalculations.Skill, as: 0, left_lateral_join: d1 in subquery(from d0 in AshPostgres.Test.ComplexCalculations.Documentation, as: 0, where: parent_as(false).id == as(0).skill_id, where: type( as(0).status, {:parameterized, Ash.Type.Atom.EctoType, one_of: [:demonstrated, :performed, :approved, :reopened]} ) == type( ^"demonstrated", {:parameterized, Ash.Type.Atom.EctoType, one_of: [:demonstrated, :performed, :approved, :reopened]} ), group_by: [d0.skill_id], select: %{ skill_id: map(d0, [:skill_id]).skill_id, count_of_demonstrated_documentations: type( coalesce( count(type(as(0).id, {:parameterized, Ash.Type.UUID.EctoType, []})), type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), on: true, where: parent_as(0).id == as(0).certification_id, where: type(as(0).removed, {:parameterized, Ash.Type.Boolean.EctoType, []}) == type(^false, {:parameterized, Ash.Type.Boolean.EctoType, []}), group_by: [s0.certification_id], select: %{ certification_id: map(s0, [:certification_id]).certification_id, count_of_skills_ever_demonstrated: type( sum( type( fragment( "(CASE WHEN ? THEN ? ELSE ? END)", type( coalesce( as(1).count_of_demonstrated_documentations, type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) == type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}), type(^0, {:parameterized, Ash.Type.Integer.EctoType, []}), type(^1, {:parameterized, Ash.Type.Integer.EctoType, []}) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), on: true, where: type(as(0).id, {:parameterized, Ash.Type.UUID.EctoType, []}) == type(^"e24ef8a5-d39d-4293-9f69-1abd10a996e6", {:parameterized, Ash.Type.UUID.EctoType, []}), select: merge( merge(struct(c0, [:id]), %{ count_of_skills_ever_demonstrated: type( type( s1.count_of_skills_ever_demonstrated, {:parameterized, Ash.Type.Integer.EctoType, []} ), {:parameterized, Ash.Type.Integer.EctoType, []} ) }), %{} ) (elixir 1.16.2) lib/enum.ex:1826: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3 (elixir 1.16.2) lib/enum.ex:2528: Enum."-reduce/3-lists^foldl/2-0-"/3 (ecto 3.11.2) lib/ecto/repo/queryable.ex:214: Ecto.Repo.Queryable.execute/4 (ecto 3.11.2) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3 (ash_postgres 2.0.2) lib/data_layer.ex:706: anonymous fn/3 in AshPostgres.DataLayer.run_query/2 (ash_postgres 2.0.2) lib/data_layer.ex:704: AshPostgres.DataLayer.run_query/2 (ash 3.0.2) lib/ash/actions/read/read.ex:2259: Ash.Actions.Read.run_query/4 (ash 3.0.2) lib/ash/actions/read/read.ex:903: Ash.Actions.Read.reselect_and_load/5 (ash 3.0.2) lib/ash/actions/read/read.ex:244: Ash.Actions.Read.do_run/3 (ash 3.0.2) lib/ash/actions/read/read.ex:66: anonymous fn/3 in Ash.Actions.Read.run/3 (ash 3.0.2) lib/ash/actions/read/read.ex:65: Ash.Actions.Read.run/3 (ash 3.0.2) lib/ash.ex:1717: Ash.load/3 (ash 3.0.2) lib/ash.ex:1687: Ash.load/3 (ash 3.0.2) lib/ash.ex:1635: Ash.load!/3 test/complex_calculations_test.exs:85: AshPostgres.Test.ComplexCalculationsTest."test complex aggregates"/1 (ex_unit 1.16.2) lib/ex_unit/runner.ex:472: ExUnit.Runner.exec_test/2 (stdlib 5.2) timer.erl:270: :timer.tc/2 (ex_unit 1.16.2) lib/ex_unit/runner.ex:394: anonymous fn/6 in ExUnit.Runner.spawn_test_monitor/4 code: |> Ash.load!([:count_of_skills_ever_demonstrated]) stacktrace: (elixir 1.16.2) lib/process.ex:860: Process.info/2 (ash 3.0.2) lib/ash/error/unknown.ex:3: Ash.Error.Unknown."exception (overridable 2)"/1 (ash 3.0.2) ash_postgres/deps/splode/lib/splode.ex:211: Ash.Error.to_class/2 (ash 3.0.2) lib/ash/error/error.ex:66: Ash.Error.to_error_class/2 (ash 3.0.2) lib/ash/actions/read/read.ex:324: Ash.Actions.Read.do_run/3 (ash 3.0.2) lib/ash/actions/read/read.ex:66: anonymous fn/3 in Ash.Actions.Read.run/3 (ash 3.0.2) lib/ash/actions/read/read.ex:65: Ash.Actions.Read.run/3 (ash 3.0.2) lib/ash.ex:1717: Ash.load/3 (ash 3.0.2) lib/ash.ex:1687: Ash.load/3 (ash 3.0.2) lib/ash.ex:1635: Ash.load!/3 test/complex_calculations_test.exs:85: (test) --- test/complex_calculations_test.exs | 24 +++++++++++++++++++ .../resources/certification.ex | 7 ++++++ .../complex_calculations/resources/skill.ex | 20 ++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 8e67e7e1..da13ab30 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -62,6 +62,30 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do assert certification.some_documentation_created end + test "complex aggregates" do + certification = + AshPostgres.Test.ComplexCalculations.Certification + |> Ash.Changeset.new() + |> Ash.create!() + + skill = + AshPostgres.Test.ComplexCalculations.Skill + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:certification, certification, type: :append) + |> Ash.create!() + + AshPostgres.Test.ComplexCalculations.Documentation + |> Ash.Changeset.for_create(:create, %{status: :demonstrated}) + |> Ash.Changeset.manage_relationship(:skill, skill, type: :append) + |> Ash.create!() + + certification = + certification + |> Ash.load!([:count_of_skills_ever_demonstrated]) + + assert certification.count_of_skills_ever_demonstrated == 1 + end + test "channel: first_member and second member" do channel = AshPostgres.Test.ComplexCalculations.Channel diff --git a/test/support/complex_calculations/resources/certification.ex b/test/support/complex_calculations/resources/certification.ex index d41ca980..6310d1c7 100644 --- a/test/support/complex_calculations/resources/certification.ex +++ b/test/support/complex_calculations/resources/certification.ex @@ -21,6 +21,13 @@ defmodule AshPostgres.Test.ComplexCalculations.Certification do count :count_of_skills, :skills do filter(expr(removed == false)) end + + sum :count_of_skills_ever_demonstrated, + :skills, + :count_ever_demonstrated do + filter(expr(removed == false)) + public?(true) + end end attributes do diff --git a/test/support/complex_calculations/resources/skill.ex b/test/support/complex_calculations/resources/skill.ex index 415a87b6..7715900e 100644 --- a/test/support/complex_calculations/resources/skill.ex +++ b/test/support/complex_calculations/resources/skill.ex @@ -14,6 +14,11 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do first :latest_documentation_status, [:documentations], :status do sort(timestamp: :desc) end + + count :count_of_demonstrated_documentations, :documentations do + filter(expr(status == :demonstrated)) + public?(true) + end end attributes do @@ -33,6 +38,21 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do ) ) end + + calculate :count_ever_demonstrated, :integer do + calculation( + expr( + if count_of_demonstrated_documentations == 0 do + 0 + else + 1 + end + ) + ) + + load([:count_of_demonstrated_documentations]) + public?(true) + end end postgres do From a750abf8c412f8a4b7941664ae3d4f6adfc52015 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Tue, 21 May 2024 04:42:56 +0200 Subject: [PATCH 0450/1215] chore: add tests for paginated relationship count (#294) --- test/load_test.exs | 145 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/test/load_test.exs b/test/load_test.exs index aa3c9c14..ffc7fc2b 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -782,5 +782,150 @@ defmodule AshPostgres.Test.LoadTest do assert %Ash.Page.Keyset{results: [%{name: "user1"}]} = Ash.page!(followers_page, :next) end + + test "it allows counting has_many relationships" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "b"}) + |> Ash.create!() + + for i <- 1..3 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id}) + |> Ash.create!() + end + + for i <- 1..6 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id}) + |> Ash.create!() + end + + paginated_posts = + Post + |> Ash.Query.for_read(:paginated) + |> Ash.Query.page(limit: 2, offset: 2, count: true) + + assert [author1, author2] = + Author + |> Ash.Query.sort(:first_name) + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!() + + assert %Ash.Page.Offset{count: 3} = author1.posts + assert %Ash.Page.Offset{count: 6} = author2.posts + end + + test "it allows counting many_to_many relationships" do + followers = + for i <- 1..9 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + followers_1_to_3 = Enum.take(followers, 3) + followers_4_to_9 = Enum.slice(followers, 3..9) + + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.Changeset.manage_relationship(:followers, followers_1_to_3, type: :append_and_remove) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "b"}) + |> Ash.Changeset.manage_relationship(:followers, followers_4_to_9, type: :append_and_remove) + |> Ash.create!() + + paginated_followers = + User + |> Ash.Query.page(limit: 2, count: true) + |> Ash.Query.sort(:name) + + assert [post1, post2] = + Post + |> Ash.Query.sort(:title) + |> Ash.Query.load(followers: paginated_followers) + |> Ash.read!() + + assert %Ash.Page.Offset{count: 3} = post1.followers + assert %Ash.Page.Offset{count: 6} = post2.followers + end + + test "allows counting nested relationships" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + _author2 = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "b"}) + |> Ash.create!() + + followers = + for i <- 1..3 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + for i <- 1..5 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id}) + |> Ash.Changeset.manage_relationship(:followers, followers, type: :append_and_remove) + |> Ash.create!() + end + + paginated_followers = + User + |> Ash.Query.page(limit: 1, count: true) + + paginated_posts = + Post + |> Ash.Query.for_read(:paginated) + |> Ash.Query.load(followers: paginated_followers) + |> Ash.Query.page(limit: 1, count: true) + + assert %Ash.Page.Offset{results: [author1], count: 2} = + Author + |> Ash.Query.sort(:first_name) + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!(page: [limit: 1, count: true]) + + assert %Ash.Page.Offset{count: 5, results: [%{followers: %Ash.Page.Offset{count: 3}}]} = + author1.posts + end + + test "doesn't leak the internal count aggregate when counting" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "a"}) + |> Ash.create!() + + for i <- 1..3 do + Post + |> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author.id}) + |> Ash.create!() + end + + paginated_posts = + Post + |> Ash.Query.for_read(:paginated) + |> Ash.Query.page(limit: 2, offset: 2, count: true) + + assert [author] = + Author + |> Ash.Query.load(posts: paginated_posts) + |> Ash.read!() + + assert %Ash.Page.Offset{count: 3} = author.posts + assert %{} == author.aggregates + end end end From 264f4c0bac11f33bc714e0e33c7d2dea61c232df Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 May 2024 17:47:10 -0400 Subject: [PATCH 0451/1215] fix: handle complex maps/list on update fix: support anonymous aggregates in sorts fix: ensure parent_as bindings properly reference binding names closes #296 closes #297 closes #298 --- .../dsls/DSL:-AshPostgres.DataLayer.md | 4 +- lib/data_layer.ex | 287 ++++++++++++------ mix.exs | 2 +- mix.lock | 4 +- test/atomics_test.exs | 43 +++ test/filter_test.exs | 14 +- test/support/resources/post.ex | 2 +- 7 files changed, 256 insertions(+), 100 deletions(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 5a73ef90..820c0b45 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -258,7 +258,7 @@ end | Name | Type | Default | Docs | |------|------|---------|------| -| [`polymorphic_on_delete`](#postgres-references-polymorphic_on_delete){: #postgres-references-polymorphic_on_delete } | `:delete \| :nilify \| {:nilify, columns} \| :nothing \| :restrict` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | +| [`polymorphic_on_delete`](#postgres-references-polymorphic_on_delete){: #postgres-references-polymorphic_on_delete } | `:delete \| :nilify \| :nothing \| :restrict \| {:nilify, atom \| list(atom)}` | | For polymorphic resources, configures the on_delete behavior of the automatically generated foreign keys to source tables. | | [`polymorphic_on_update`](#postgres-references-polymorphic_on_update){: #postgres-references-polymorphic_on_update } | `:update \| :nilify \| :nothing \| :restrict` | | For polymorphic resources, configures the on_update behavior of the automatically generated foreign keys to source tables. | @@ -297,7 +297,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post | Name | Type | Default | Docs | |------|------|---------|------| | [`ignore?`](#postgres-references-reference-ignore?){: #postgres-references-reference-ignore? } | `boolean` | | If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way | -| [`on_delete`](#postgres-references-reference-on_delete){: #postgres-references-reference-on_delete } | `:delete \| :nilify \| {:nilify, columns} \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | +| [`on_delete`](#postgres-references-reference-on_delete){: #postgres-references-reference-on_delete } | `:delete \| :nilify \| :nothing \| :restrict \| {:nilify, atom \| list(atom)}` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | | [`on_update`](#postgres-references-reference-on_update){: #postgres-references-reference-on_update } | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | | [`deferrable`](#postgres-references-reference-deferrable){: #postgres-references-reference-deferrable } | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | | [`name`](#postgres-references-reference-name){: #postgres-references-reference-name } | `String.t` | | The name of the foreign key to generate in the database. Defaults to
__fkey | diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1c9f7ec5..2d8d63b3 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1,4 +1,6 @@ defmodule AshPostgres.DataLayer do + require Ecto.Query + @manage_tenant %Spark.Dsl.Section{ name: :manage_tenant, describe: """ @@ -408,105 +410,136 @@ defmodule AshPostgres.DataLayer do show_for_repo? = Enum.count_until(repos, 2) == 2 for repo <- repos do - for_repo = - if show_for_repo? do - " for repo #{inspect(repo)}" - else - "" - end - - migrations_path = AshPostgres.Mix.Helpers.migrations_path([], repo) - tenant_migrations_path = AshPostgres.Mix.Helpers.tenant_migrations_path([], repo) - - files = - migrations_path - |> Path.join("**/*.exs") - |> Path.wildcard() - |> Enum.sort() - |> Enum.reverse() - |> Enum.take(20) - |> Enum.map(&String.trim_leading(&1, migrations_path)) - |> Enum.with_index() - |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - - n = - Mix.shell().prompt( - """ - How many migrations should be rolled back#{for_repo}? (default: 0) - - Last 20 migration names, with the input you must provide to - rollback up to *and including* that migration: + {:ok, _, _} = + Ecto.Migrator.with_repo(repo, fn repo -> + for_repo = + if show_for_repo? do + " for repo #{inspect(repo)}" + else + "" + end - #{Enum.join(files, "\n")} - Rollback to: - """ - |> String.trim_trailing() - ) - |> String.trim() - |> case do - "" -> - 0 + migrations_path = AshPostgres.Mix.Helpers.migrations_path([], repo) + tenant_migrations_path = AshPostgres.Mix.Helpers.tenant_migrations_path([], repo) - n -> - try do - String.to_integer(n) - rescue - _ -> - reraise "Required an integer value, got: #{n}", __STACKTRACE__ + current_migrations = + Ecto.Query.from(row in "schema_migrations", + select: row.version + ) + |> repo.all() + |> Enum.map(&to_string/1) + + files = + migrations_path + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.sort() + |> Enum.reverse() + |> Enum.filter(fn file -> + Enum.any?(current_migrations, &String.starts_with?(file, &1)) + end) + |> Enum.take(20) + |> Enum.map(&String.trim_leading(&1, migrations_path)) + |> Enum.with_index() + |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) + + n = + Mix.shell().prompt( + """ + How many migrations should be rolled back#{for_repo}? (default: 0) + + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: + + #{Enum.join(files, "\n")} + Rollback to: + """ + |> String.trim_trailing() + ) + |> String.trim() + |> case do + "" -> + 0 + + n -> + try do + String.to_integer(n) + rescue + _ -> + reraise "Required an integer value, got: #{n}", __STACKTRACE__ + end end - end - - Mix.Task.run("ash_postgres.rollback", args ++ ["-r", inspect(repo), "-n", to_string(n)]) - Mix.Task.reenable("ash_postgres.rollback") - - tenant_files = - tenant_migrations_path - |> Path.join("**/*.exs") - |> Path.wildcard() - |> Enum.sort() - |> Enum.reverse() - |> Enum.take(20) - |> Enum.map(&String.trim_leading(&1, tenant_migrations_path)) - |> Enum.with_index() - |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - if !Enum.empty?(tenant_files) do - n = - Mix.shell().prompt( - """ + Mix.Task.run("ash_postgres.rollback", args ++ ["-r", inspect(repo), "-n", to_string(n)]) + Mix.Task.reenable("ash_postgres.rollback") - How many _tenant_ migrations should be rolled back#{for_repo}? (default: 0) + first_tenant = repo.list_tenants() |> Enum.at(0) - Last 20 migration names, with the input you must provide to - rollback up to *and including* that migration: + if first_tenant do + current_tenant_migrations = + Ecto.Query.from(row in "schema_migrations", + select: row.version + ) + |> repo.all(prefix: first_tenant) + |> Enum.map(&to_string/1) + + tenant_files = + tenant_migrations_path + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.sort() + |> Enum.reverse() + |> Enum.filter(fn file -> + Enum.any?(current_tenant_migrations, &String.starts_with?(file, &1)) + end) + |> Enum.take(20) + |> Enum.map(&String.trim_leading(&1, tenant_migrations_path)) + |> Enum.with_index() + |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) + + if !Enum.empty?(tenant_files) do + n = + Mix.shell().prompt( + """ + + How many _tenant_ migrations should be rolled back#{for_repo}? (default: 0) + + IMPORTANT: we are assuming that all of your tenants have all had the same migrations run. + If each tenant may be in a different state: *abort this command and roll them back individually*. + To do so, use the `--only-tenants` option to `mix ash_postgres.rollback`. + + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: + + #{Enum.join(tenant_files, "\n")} + + Rollback to: + """ + |> String.trim_trailing() + ) + |> String.trim() + |> case do + "" -> + 0 + + n -> + try do + String.to_integer(n) + rescue + _ -> + reraise "Required an integer value, got: #{n}", __STACKTRACE__ + end + end - #{Enum.join(tenant_files, "\n")} + Mix.Task.run( + "ash_postgres.rollback", + args ++ ["--tenants", "-r", inspect(repo), "-n", to_string(n)] + ) - Rollback to: - """ - |> String.trim_trailing() - ) - |> String.trim() - |> case do - "" -> - 0 - - n -> - try do - String.to_integer(n) - rescue - _ -> - reraise "Required an integer value, got: #{n}", __STACKTRACE__ - end + Mix.Task.reenable("ash_postgres.rollback") + end end - - Mix.Task.run( - "ash_postgres.rollback", - args ++ ["--tenants", "-r", inspect(repo), "-n", to_string(n)] - ) - - Mix.Task.reenable("ash_postgres.rollback") - end + end) end end @@ -889,6 +922,9 @@ defmodule AshPostgres.DataLayer do _destination_resource, path ) do + {calculations_require_rewrite, aggregates_require_rewrite, query} = + rewrite_nested_selects(query) + case lateral_join_query( query, root_data, @@ -909,7 +945,7 @@ defmodule AshPostgres.DataLayer do lateral_join_query, AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, source_resource) ) - |> remap_mapped_fields(query) + |> remap_mapped_fields(query, calculations_require_rewrite, aggregates_require_rewrite) {:ok, results} @@ -918,21 +954,86 @@ defmodule AshPostgres.DataLayer do end end - defp remap_mapped_fields(results, query) do + defp rewrite_nested_selects(query) do + case query.select do + %Ecto.Query.SelectExpr{ + expr: + {:merge, [], + [ + {:&, [], [0]}, + {:%{}, [], merging} + ]} + } = select -> + {merging, aggregate_merges} = remap_sub_select(merging, :aggregates) + + {new_sub_selects, calculation_merges} = + remap_sub_select(merging, :calculations) + + new_query = + %{ + query + | select: %{select | expr: {:merge, [], [{:&, [], [0]}, {:%{}, [], new_sub_selects}]}} + } + + {calculation_merges, aggregate_merges, new_query} + + _ -> + {%{}, %{}, query} + end + end + + # sobelow_skip ["DOS.StringToAtom"] + defp remap_sub_select(merging, sub_key) do + case Keyword.fetch(merging, sub_key) do + {:ok, {:%{}, [], nested}} -> + Enum.reduce(nested, {Keyword.delete(merging, sub_key), %{}}, fn {name, expr}, + {subselect, remapping} -> + new_name = String.to_atom("__#{sub_key}__#{name}") + {Keyword.put(subselect, new_name, expr), Map.put(remapping, new_name, name)} + end) + + :error -> + {merging, %{}} + end + end + + defp remap_mapped_fields( + results, + query, + calculations_require_rewrite \\ %{}, + aggregates_require_rewrite \\ %{} + ) do calculation_names = query.__ash_bindings__.calculation_names aggregate_names = query.__ash_bindings__.aggregate_names - if Enum.empty?(calculation_names) and Enum.empty?(aggregate_names) do + if Enum.empty?(calculation_names) and Enum.empty?(aggregate_names) and + Enum.empty?(calculations_require_rewrite) and Enum.empty?(aggregates_require_rewrite) do results else Enum.map(results, fn result -> result + |> remap_to_nested(:calculations, calculations_require_rewrite) + |> remap_to_nested(:aggregates, aggregates_require_rewrite) |> remap(:calculations, calculation_names) |> remap(:aggregates, aggregate_names) end) end end + defp remap_to_nested(record, _subfield, mapping) when mapping == %{} do + record + end + + defp remap_to_nested(record, subfield, mapping) do + Map.update!(record, subfield, fn subfield_values -> + Enum.reduce(mapping, subfield_values, fn {source, dest}, subfield_values -> + subfield_values + |> Map.put(dest, Map.get(record, source)) + |> Map.delete(source) + end) + end) + end + defp remap(record, _subfield, mapping) when mapping == %{} do record end diff --git a/mix.exs b/mix.exs index 2164798e..8dd000db 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0")}, - {:ash_sql, ash_sql_version("~> 0.1")}, + {:ash_sql, ash_sql_version("~> 0.1 and >= 0.1.3")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 7a3b0d0e..fa8eb60c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.2", "2b3bec4c53a04ed8d415052cc992b6c76e0e348fa2fae06cdb28d6769709438c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "784b6a3ade4b0c8e5c16cd5792ab080affef8729d48e32c15e4e283c93696104"}, - "ash_sql": {:hex, :ash_sql, "0.1.2", "a479348d46bf19ec0c981432a47ae8f15781d559bfc708a0260853acb8cc848b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "35fab32c2e5316fac8552dd541707301c616bf548735af593581dcebc76215eb"}, + "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -30,7 +30,7 @@ "reactor": {:hex, :reactor, "0.8.2", "b2be82b1c3402537d06a8f85bb1849f72cb6b4be140495cb8956de7aec2fdebd", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c35eb23b77cc77ba922af108722ac93257899e35cfdd18882f0e659ad2cac9f3"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.1.0", "9c129fa1bd7290014acf6f73e292f43938c17e3fccd7b7df6f41122cab45dda9", [:mix], [], "hexpm", "b9c348688e2cfc20acfef0feaca88643044be5acd2e0b02cf4a8d6ac1edc4c4a"}, + "sourceror": {:hex, :sourceror, "1.2.0", "471232b2eb9ab930b90673d37cf005bbaec0ef02dadf5bf4c8c00c3d75a6c131", [:mix], [], "hexpm", "f01796ce1b87016573ce7b66073d6b48297c4d233982340340834269b8c95e51"}, "spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index a311f7c6..a1221ae4 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -35,6 +35,49 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "atomics work with maps that contain lists" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Ash.create!() + + assert %{list_of_stuff: [%{"foo" => [%{"a" => 1}]}]} = + post + |> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{foo: [%{a: 1}]}]}) + |> Ash.update!() + end + + test "atomics work with maps that contain lists that contain maps that contain lists etc." do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Ash.create!() + + assert %{list_of_stuff: [%{"foo" => [%{"a" => 1, "b" => %{"c" => [1, 2, 3]}}]}]} = + post + |> Ash.Changeset.for_update(:update, %{ + list_of_stuff: [%{foo: [%{a: 1, b: %{c: [1, 2, 3]}}]}] + }) + |> Ash.update!() + end + + test "atomics work with maps that contain expressions in a deep structure" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Ash.create!() + + assert %{list_of_stuff: [%{"foo" => [%{"a" => 1, "b" => %{"c" => [1, 2, 3]}}]}]} = + post + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.atomic_update(%{ + list_of_stuff: [ + %{foo: [%{a: 1, b: %{c: [1, 2, expr(type(fragment("3"), :integer))]}}]} + ] + }) + |> Ash.update!() + end + test "an atomic validation is based on where it appears in the action" do post = Post diff --git a/test/filter_test.exs b/test/filter_test.exs index 6bdc5edb..a1abe491 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.FilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Organization, Post} + alias AshPostgres.Test.{Author, Comment, Organization, Post, PostLink} alias AshPostgres.Test.ComplexCalculations.{Channel, ChannelMember} require Ash.Query @@ -652,6 +652,18 @@ defmodule AshPostgres.FilterTest do |> Ash.Changeset.manage_relationship(:linked_posts, [post], type: :append_and_remove) |> Ash.create!() + other_post = + Post + |> Ash.Changeset.for_create(:create, %{title: "b"}) + |> Ash.create!() + + PostLink + |> Ash.Changeset.for_create(:create, %{ + source_post_id: post.id, + destination_post_id: other_post.id + }) + |> Ash.create!() + assert [%{title: "b"}] = Post |> Ash.Query.filter(exists(linked_posts, title == ^"a")) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 8ee5f0d6..511857ff 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -322,7 +322,7 @@ defmodule AshPostgres.Test.Post do join_relationship: :post_followers, source_attribute_on_join_resource: :post_id, destination_attribute_on_join_resource: :follower_id, - sort: [Ash.Sort.expr_sort(parent(post_followers.order))] + sort: [Ash.Sort.expr_sort(parent(post_followers.order), :integer)] ) has_many(:views, AshPostgres.Test.PostView) do From a372b6fd5543eb437ca51aa3de880b1b97eac2fd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 May 2024 17:49:20 -0400 Subject: [PATCH 0452/1215] chore: update ash --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 8dd000db..2ab4d100 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0")}, + {:ash, ash_version("~> 3.0 and >= 3.0.3")}, {:ash_sql, ash_sql_version("~> 0.1 and >= 0.1.3")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index fa8eb60c..60105c9f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.2", "2b3bec4c53a04ed8d415052cc992b6c76e0e348fa2fae06cdb28d6769709438c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "784b6a3ade4b0c8e5c16cd5792ab080affef8729d48e32c15e4e283c93696104"}, + "ash": {:hex, :ash, "3.0.3", "3684c3def2e3014a7547efec1fea6a7ed1e2c4ef89268f20509bdb3547591417", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a358b0dcfc81302da92acf4ebeef75f3e4728e7d5042341abe9137984fbafeb"}, "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 249fa7a89a8acc79b5be14b447527f0d0991a9ae Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 May 2024 17:49:49 -0400 Subject: [PATCH 0453/1215] chore: release version v2.0.3 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c55813..5ed17d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.3](https://github.com/ash-project/ash_postgres/compare/v2.0.2...v2.0.3) (2024-05-22) + + + + +### Bug Fixes: + +* handle complex maps/list on update + +* support anonymous aggregates in sorts + +* ensure parent_as bindings properly reference binding names + +* add and remove custom indexes in tandem properly + +### Improvements: + +* support `on_delete: :nilify` for specific columns (#289) + ## [v2.0.2](https://github.com/ash-project/ash_postgres/compare/v2.0.1...v2.0.2) (2024-05-15) diff --git a/mix.exs b/mix.exs index 2ab4d100..e599279f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.2" + @version "2.0.3" def project do [ From cd9201af0522c7697c1d3dbb1d4ae76ac0d57f22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 09:24:27 -0400 Subject: [PATCH 0454/1215] chore(deps): bump ash from 3.0.3 to 3.0.4 (#300) Bumps [ash](https://github.com/ash-project/ash) from 3.0.3 to 3.0.4. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/3.0.3...v3.0.4) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 60105c9f..6ab569d7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.3", "3684c3def2e3014a7547efec1fea6a7ed1e2c4ef89268f20509bdb3547591417", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a358b0dcfc81302da92acf4ebeef75f3e4728e7d5042341abe9137984fbafeb"}, + "ash": {:hex, :ash, "3.0.4", "afa8e7089da6e9333b637a62b585ba7360e5e5190cccf20976fe6d4470a568a6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b74eebe07285cc6b810d08a3ecfbf0fc9821aaad96d3bda1918d8a510a237a3e"}, "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From e891c20b4f5a461f49dcc35d9899c502c65854c5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 May 2024 16:33:55 -0400 Subject: [PATCH 0455/1215] test: add tests for `exists` calculation --- test/calculation_test.exs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 7ed741cf..f35774a6 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -298,6 +298,15 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + test ".calculate works with `exists`" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Ash.calculate!(post, :author_has_post_with_follower_named_fred) + end + test "calculations that refer to aggregates can be authorized" do post = Post From 083807888cda0dcf3ec1006ea48b644881172644 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 May 2024 18:41:23 -0400 Subject: [PATCH 0456/1215] fix: ensure update's reselect all changing values test: add calculation tests --- lib/data_layer.ex | 8 +++----- mix.exs | 2 +- mix.lock | 2 +- test/calculation_test.exs | 7 ++++++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2d8d63b3..c00f4896 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2600,14 +2600,13 @@ defmodule AshPostgres.DataLayer do ) |> pkey_filter(changeset.data) - select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) - case bulk_updatable_query(query, resource, changeset.atomics, [], changeset.context) do {:error, error} -> {:error, error} {:ok, query} -> - query = Ecto.Query.select(query, ^select) + modifying = Map.keys(changeset.attributes) ++ Keyword.keys(changeset.atomics) + query = Ecto.Query.select(query, ^modifying) try do case AshSql.Atomics.query_with_atomics( @@ -2656,8 +2655,7 @@ defmodule AshPostgres.DataLayer do {1, [result]} -> record = changeset.data - |> Map.merge(changeset.attributes) - |> Map.merge(Map.take(result, Keyword.keys(changeset.atomics))) + |> Map.merge(Map.take(result, modifying)) maybe_update_tenant(resource, changeset, record) diff --git a/mix.exs b/mix.exs index e599279f..d899fae0 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0 and >= 3.0.3")}, + {:ash, ash_version("~> 3.0 and >= 3.0.6")}, {:ash_sql, ash_sql_version("~> 0.1 and >= 0.1.3")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index 6ab569d7..11fcf210 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.4", "afa8e7089da6e9333b637a62b585ba7360e5e5190cccf20976fe6d4470a568a6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b74eebe07285cc6b810d08a3ecfbf0fc9821aaad96d3bda1918d8a510a237a3e"}, + "ash": {:hex, :ash, "3.0.6", "888a5b81a0106e7122a487ea55cd968d8acfc5ac85a22c876eef7ffea9083041", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c3d529933cfe53fb9e018f04cc7106eef74d74872edee1288d0ba75d9e97202d"}, "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index f35774a6..186cf112 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -304,7 +304,12 @@ defmodule AshPostgres.CalculationTest do |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.create!() - Ash.calculate!(post, :author_has_post_with_follower_named_fred) + assert_raise Ash.Error.Invalid, ~r/Primary key is required for/, fn -> + refute Ash.calculate!(Post, :author_has_post_with_follower_named_fred) + end + + refute Ash.calculate!(post, :author_has_post_with_follower_named_fred) + refute Ash.calculate!(Post, :author_has_post_with_follower_named_fred, refs: %{id: post.id}) end test "calculations that refer to aggregates can be authorized" do From 4e84a3f75ded07867a62fd2e19fdce85e530866b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 May 2024 18:42:16 -0400 Subject: [PATCH 0457/1215] chore: release version v2.0.4 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed17d5b..03e644ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.4](https://github.com/ash-project/ash_postgres/compare/v2.0.3...v2.0.4) (2024-05-23) + + + + +### Bug Fixes: + +* ensure update's reselect all changing values + ## [v2.0.3](https://github.com/ash-project/ash_postgres/compare/v2.0.2...v2.0.3) (2024-05-22) diff --git a/mix.exs b/mix.exs index d899fae0..014eea23 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.3" + @version "2.0.4" def project do [ From 8ad92cc3c07264c45d8ced2919d1f1b740838680 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 24 May 2024 01:14:55 -0400 Subject: [PATCH 0458/1215] improvement: update `ash` and support new `identity` features --- .formatter.exs | 2 + CHANGELOG.md | 29 +- .../dsls/DSL:-AshPostgres.DataLayer.md | 2 + lib/data_layer.ex | 10 + lib/data_layer/info.ex | 18 + .../migration_generator.ex | 39 +- lib/migration_generator/operation.ex | 19 +- mix.exs | 2 +- mix.lock | 4 +- .../test_repo/posts/20240524031113.json | 371 ++++++++++++++++ .../test_repo/posts/20240524041750.json | 396 ++++++++++++++++++ .../20240524031113_migrate_resources25.exs | 32 ++ .../20240524041750_migrate_resources26.exs | 54 +++ test/migration_generator_test.exs | 30 +- test/support/resources/post.ex | 12 + 15 files changed, 971 insertions(+), 49 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20240524031113.json create mode 100644 priv/resource_snapshots/test_repo/posts/20240524041750.json create mode 100644 priv/test_repo/migrations/20240524031113_migrate_resources25.exs create mode 100644 priv/test_repo/migrations/20240524041750_migrate_resources26.exs diff --git a/.formatter.exs b/.formatter.exs index c57f31c5..786ee5de 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,6 +1,7 @@ spark_locals_without_parens = [ all_tenants?: 1, base_filter_sql: 1, + calculations_to_sql: 1, check: 1, check_constraint: 2, check_constraint: 3, @@ -13,6 +14,7 @@ spark_locals_without_parens = [ exclusion_constraint_names: 1, foreign_key_names: 1, identity_index_names: 1, + identity_wheres_to_sql: 1, ignore?: 1, include: 1, index: 1, diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e644ca..531ba070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,46 +7,37 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.0.4](https://github.com/ash-project/ash_postgres/compare/v2.0.3...v2.0.4) (2024-05-23) - - - ### Bug Fixes: -* ensure update's reselect all changing values +[updates] ensure update's reselect all changing values ## [v2.0.3](https://github.com/ash-project/ash_postgres/compare/v2.0.2...v2.0.3) (2024-05-22) - - - ### Bug Fixes: -* handle complex maps/list on update +[updates] handle complex maps/list on update -* support anonymous aggregates in sorts +[Ash.Query] support anonymous aggregates in sorts -* ensure parent_as bindings properly reference binding names +[exists] ensure parent_as bindings properly reference binding names -* add and remove custom indexes in tandem properly +[migration generator] add and remove custom indexes in tandem properly ### Improvements: -* support `on_delete: :nilify` for specific columns (#289) +[references] support `on_delete: :nilify` for specific columns (#289) ## [v2.0.2](https://github.com/ash-project/ash_postgres/compare/v2.0.1...v2.0.2) (2024-05-15) - - - ### Bug Fixes: -* [update_query/destroy_query] rework the update and destroy query builder to support multiple kinds of joining +- [update_query/destroy_query] rework the update and destroy query builder to support multiple kinds of joining -* [mix ash_postgres.migrate] remove duplicate repo flags (#285) +- [mix ash_postgres.migrate] remove duplicate repo flags (#285) -* [Ash.Error.Changes.StaleRecord] ensure filter is included in stale record error messages we return +- [Ash.Error.Changes.StaleRecord] ensure filter is included in stale record error messages we return -* [AshPostgres.MigrationGenerator] properly parse previous version from migration generation +- [AshPostgres.MigrationGenerator] properly parse previous version from migration generation ## [v2.0.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0...v2.0.1) (2024-05-12) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 820c0b45..7d6cde44 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -42,6 +42,8 @@ end | [`migrate?`](#postgres-migrate?){: #postgres-migrate? } | `boolean` | `true` | Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` | | [`migration_types`](#postgres-migration_types){: #postgres-migration_types } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | | [`migration_defaults`](#postgres-migration_defaults){: #postgres-migration_defaults } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | +| [`calculations_to_sql`](#postgres-calculations_to_sql){: #postgres-calculations_to_sql } | `keyword` | | A keyword list of calculations and their SQL representation. Used when creating unique indexes for identities over calculations | +| [`identity_wheres_to_sql`](#postgres-identity_wheres_to_sql){: #postgres-identity_wheres_to_sql } | `keyword` | | A keyword list of identity names and the SQL representation of their `where` clause. Used when creating unique indexes for identities over calculations | | [`base_filter_sql`](#postgres-base_filter_sql){: #postgres-base_filter_sql } | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | | [`simple_join_first_aggregates`](#postgres-simple_join_first_aggregates){: #postgres-simple_join_first_aggregates } | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | | [`skip_unique_indexes`](#postgres-skip_unique_indexes){: #postgres-skip_unique_indexes } | `atom \| list(atom)` | `false` | Skip generating unique indexes when generating migrations | diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c00f4896..10839a4b 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -300,6 +300,16 @@ defmodule AshPostgres.DataLayer do A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\\\"now()\\\\")`, or for `nil`, use `\\\\"nil\\\\"`. """ ], + calculations_to_sql: [ + type: :keyword_list, + doc: + "A keyword list of calculations and their SQL representation. Used when creating unique indexes for identities over calculations" + ], + identity_wheres_to_sql: [ + type: :keyword_list, + doc: + "A keyword list of identity names and the SQL representation of their `where` clause. Used when creating unique indexes for identities over calculations" + ], base_filter_sql: [ type: :string, doc: diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 8ed9f368..b16c252f 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -14,6 +14,24 @@ defmodule AshPostgres.DataLayer.Info do end end + @doc "A keyword list of calculations to their sql representation" + def calculations_to_sql(resource) do + Extension.get_opt(resource, [:postgres], :calculations_to_sql, []) + end + + def calculation_to_sql(resource, calc) do + calculations_to_sql(resource)[calc] + end + + @doc "A keyword list of identity names to the sql representation of their where clauses" + def identity_wheres_to_sql(resource) do + Extension.get_opt(resource, [:postgres], :identity_wheres_to_sql, []) + end + + def identity_where_to_sql(resource, identity) do + identity_wheres_to_sql(resource)[identity] + end + @doc "Checks a version requirement against the resource's repo's postgres version" def pg_version_matches?(resource, requirement) do resource diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index e00041c3..eefe3744 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1817,7 +1817,9 @@ defmodule AshPostgres.MigrationGenerator do old_identity.name == identity.name && Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && old_identity.base_filter == identity.base_filter && - old_identity.all_tenants? == identity.all_tenants? + old_identity.all_tenants? == identity.all_tenants? && + old_identity.nils_distinct? == identity.nils_distinct? && + old_identity.where == identity.where end) else false @@ -1829,7 +1831,9 @@ defmodule AshPostgres.MigrationGenerator do old_identity.name == identity.name && Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && old_identity.base_filter == identity.base_filter && - old_identity.all_tenants? == identity.all_tenants? + old_identity.all_tenants? == identity.all_tenants? && + old_identity.nils_distinct? == identity.nils_distinct? && + old_identity.where == identity.where end) end) end @@ -2820,23 +2824,32 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.reject(fn identity -> identity.name in AshPostgres.DataLayer.Info.skip_unique_indexes(resource) end) - |> Enum.filter(fn identity -> - Enum.all?(identity.keys, fn key -> - Ash.Resource.Info.attribute(resource, key) - end) - end) |> Enum.sort_by(& &1.name) - |> Enum.map(&Map.take(&1, [:name, :keys, :all_tenants?])) |> Enum.map(fn %{keys: keys} = identity -> %{ identity | keys: Enum.map(keys, fn key -> - attribute = Ash.Resource.Info.attribute(resource, key) - attribute.source || attribute.name + case Ash.Resource.Info.field(resource, key) do + %Ash.Resource.Attribute{} = attribute -> + to_string(attribute.source || attribute.name) + + %Ash.Resource.Calculation{} -> + AshPostgres.DataLayer.Info.calculation_to_sql(resource, key) || + raise "Must define an entry for :#{key} in `postgres.calculations_to_sql`, or skip this identity with `postgres.skip_unique_indexes`" + end end) + |> Enum.sort(), + where: + if identity.where do + AshPostgres.DataLayer.Info.identity_where_to_sql(resource, identity.name) || + raise( + "Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql`, or skip this identity with `postgres.skip_unique_indexes`" + ) + end } end) + |> Enum.map(&Map.take(&1, [:name, :keys, :where, :all_tenants?, :nils_distinct?])) |> Enum.map(fn identity -> Map.put( identity, @@ -3179,13 +3192,13 @@ defmodule AshPostgres.MigrationGenerator do identity |> Map.update!(:name, &maybe_to_atom/1) |> Map.update!(:keys, fn keys -> - keys - |> Enum.map(&maybe_to_atom/1) - |> Enum.sort() + Enum.sort(keys) end) |> add_index_name(table) |> Map.put_new(:base_filter, nil) |> Map.put_new(:all_tenants?, false) + |> Map.put_new(:where, nil) + |> Map.put_new(:nils_distinct?, true) end defp add_index_name(%{name: name} = index, table) do diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 55005251..97f225b2 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -30,7 +30,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do # sobelow_skip ["DOS.StringToAtom"] def as_atom(value), do: Macro.inspect_atom(:remote_call, String.to_atom(value)) - def option(:nulls_distinct = key, value) do + def option(key, value) when key in [:nulls_distinct, "nulls_distinct"] do if !value do "#{as_atom(key)}: #{inspect(value)}" end @@ -790,6 +790,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do identity: %{ name: name, keys: keys, + nils_distinct?: nils_distinct?, + where: where, base_filter: base_filter, index_name: index_name, all_tenants?: all_tenants? @@ -813,10 +815,17 @@ defmodule AshPostgres.MigrationGenerator.Operation do index_name = index_name || "#{table}_#{name}_index" - if base_filter do - "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], where: \"#{base_filter}\", #{join(["name: \"#{index_name}\"", option("prefix", schema)])})" - else - "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema)])})" + cond do + base_filter && where -> + where = "(#{where}) AND (#{base_filter})" + + "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?), option("where", where)])})" + + base_filter -> + "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], where: \"#{base_filter}\", #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?)])})" + + true -> + "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?), option("where", where)])})" end end diff --git a/mix.exs b/mix.exs index 014eea23..3920eb45 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0 and >= 3.0.6")}, + {:ash, ash_version("~> 3.0 and >= 3.0.7")}, {:ash_sql, ash_sql_version("~> 0.1 and >= 0.1.3")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index 11fcf210..bd556f8a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.6", "888a5b81a0106e7122a487ea55cd968d8acfc5ac85a22c876eef7ffea9083041", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c3d529933cfe53fb9e018f04cc7106eef74d74872edee1288d0ba75d9e97202d"}, + "ash": {:hex, :ash, "3.0.7", "6c37e092f53b1b21eb89596f600a652b2a601f84378f44fd5dd1cdec72eb1cc2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9288ddb50fe727096c6f63fd82c631de2505dcd29bdfa50b5dc13c865f0bf434"}, "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -30,7 +30,7 @@ "reactor": {:hex, :reactor, "0.8.2", "b2be82b1c3402537d06a8f85bb1849f72cb6b4be140495cb8956de7aec2fdebd", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c35eb23b77cc77ba922af108722ac93257899e35cfdd18882f0e659ad2cac9f3"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.2.0", "471232b2eb9ab930b90673d37cf005bbaec0ef02dadf5bf4c8c00c3d75a6c131", [:mix], [], "hexpm", "f01796ce1b87016573ce7b66073d6b48297c4d233982340340834269b8c95e51"}, + "sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"}, "spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/priv/resource_snapshots/test_repo/posts/20240524031113.json b/priv/resource_snapshots/test_repo/posts/20240524031113.json new file mode 100644 index 00000000..068faaf2 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240524031113.json @@ -0,0 +1,371 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "timestamptz(6)", + "source": "datetime", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_on_upper", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "timestamptz(6)", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "A602EBEA21CE56CC203A0FC8EA66D35D5FD2DA646411088CE343EF793C9D3E9C", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + }, + { + "name": "uniq_on_upper", + "keys": [ + "UPPER(uniq_on_upper)" + ], + "base_filter": "type = 'sponsored'", + "all_tenants?": false, + "index_name": "posts_uniq_on_upper_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "prefix": null, + "where": null, + "unique": true, + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20240524041750.json b/priv/resource_snapshots/test_repo/posts/20240524041750.json new file mode 100644 index 00000000..ab0cbe81 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240524041750.json @@ -0,0 +1,396 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "generated?": false, + "primary_key?": true, + "allow_nil?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "timestamptz(6)", + "source": "datetime", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_on_upper", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_if_contains_foo", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "timestamptz(6)", + "source": "updated_at", + "references": null, + "generated?": false, + "primary_key?": false, + "allow_nil?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "on_delete": null, + "on_update": null, + "destination_attribute_generated": null, + "destination_attribute_default": null, + "primary_key?": true, + "destination_attribute": "id", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "match_with": null, + "match_type": null, + "schema": "public", + "deferrable": false + }, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "on_delete": null, + "on_update": null, + "destination_attribute_generated": null, + "destination_attribute_default": null, + "primary_key?": true, + "destination_attribute": "id", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "match_with": null, + "match_type": null, + "schema": "public", + "deferrable": false + }, + "generated?": false, + "primary_key?": false, + "allow_nil?": true + } + ], + "table": "posts", + "hash": "1C2BE60C682696F09AC2505B2B8844DFA449834F0E05B4C69D8E8F40B8C9CA89", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "schema": null, + "base_filter": "type = 'sponsored'", + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "where": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "unique": true, + "nulls_distinct": true, + "all_tenants?": false, + "concurrently": true, + "using": null, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ] + } + ], + "has_create_action": true, + "identities": [ + { + "name": "uniq_on_upper", + "keys": [ + "UPPER(uniq_on_upper)" + ], + "where": null, + "nils_distinct?": true, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "all_tenants?": false + }, + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "where": null, + "nils_distinct?": true, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "all_tenants?": false + }, + { + "name": "uniq_if_contains_foo", + "keys": [ + "uniq_if_contains_foo" + ], + "where": "(uniq_if_contains_foo LIKE '%foo%')", + "nils_distinct?": true, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "all_tenants?": false + } + ], + "custom_statements": [], + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ] +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240524031113_migrate_resources25.exs b/priv/test_repo/migrations/20240524031113_migrate_resources25.exs new file mode 100644 index 00000000..9c524d71 --- /dev/null +++ b/priv/test_repo/migrations/20240524031113_migrate_resources25.exs @@ -0,0 +1,32 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources25 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(:posts) do + add(:uniq_on_upper, :text) + end + + create( + unique_index(:posts, ["UPPER(uniq_on_upper)"], + where: "type = 'sponsored'", + name: "posts_uniq_on_upper_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:posts, ["UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") + ) + + alter table(:posts) do + remove(:uniq_on_upper) + end + end +end diff --git a/priv/test_repo/migrations/20240524041750_migrate_resources26.exs b/priv/test_repo/migrations/20240524041750_migrate_resources26.exs new file mode 100644 index 00000000..a65fb877 --- /dev/null +++ b/priv/test_repo/migrations/20240524041750_migrate_resources26.exs @@ -0,0 +1,54 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources26 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(:posts) do + add(:uniq_if_contains_foo, :text) + end + + drop_if_exists( + unique_index(:posts, [:"UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") + ) + + create( + unique_index(:posts, ["UPPER(uniq_on_upper)"], + where: "type = 'sponsored'", + name: "posts_uniq_on_upper_index" + ) + ) + + create( + unique_index(:posts, [:uniq_if_contains_foo], + name: "posts_uniq_if_contains_foo_index", + where: "((uniq_if_contains_foo LIKE '%foo%')) AND (type = 'sponsored')" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:posts, [:uniq_if_contains_foo], name: "posts_uniq_if_contains_foo_index") + ) + + drop_if_exists( + unique_index(:posts, ["UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") + ) + + create( + unique_index(:posts, [:"UPPER(uniq_on_upper)"], + where: "type = 'sponsored'", + name: "posts_uniq_on_upper_index" + ) + ) + + alter table(:posts) do + remove(:uniq_if_contains_foo) + end + end +end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 11b6758f..596f2d55 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -155,15 +155,16 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file_contents =~ ~S[add :second_title, :varchar, size: 16] # the migration creates unique_indexes based on the identities of the resource - assert file_contents =~ ~S{create unique_index(:posts, [:title], name: "posts_title_index")} + assert file_contents =~ + ~S{create unique_index(:posts, ["title"], name: "posts_title_index")} # the migration creates unique_indexes based on the identities of the resource assert file_contents =~ - ~S{create unique_index(:posts, [:title, :second_title], name: "posts_thing_index")} + ~S{create unique_index(:posts, ["second_title", "title"], name: "posts_thing_index")} # the migration creates unique_indexes using the `source` on the attributes of the identity on the resource assert file_contents =~ - ~S{create unique_index(:posts, [:title, :t_w_s], name: "posts_thing_with_source_index")} + ~S{create unique_index(:posts, ["t_w_s", "title"], name: "posts_thing_with_source_index")} end end @@ -177,11 +178,19 @@ defmodule AshPostgres.MigrationGeneratorTest do defposts do postgres do migration_types(second_title: {:varchar, 16}) + + identity_wheres_to_sql(second_title: "(second_title like '%foo%')") + schema("example") end identities do identity(:title, [:title]) + + identity :second_title, [:second_title] do + nils_distinct?(false) + where expr(contains(second_title, "foo")) + end end attributes do @@ -231,6 +240,9 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file_contents =~ ~S{create index(:posts, ["id"]} + assert file_contents =~ + ~S{create unique_index(:posts, ["second_title"], name: "posts_second_title_index", prefix: "example", nulls_distinct: false, where: "(second_title like '%foo%')")} + # the migration adds the id, with its default assert file_contents =~ ~S[add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true] @@ -243,7 +255,7 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration creates unique_indexes based on the identities of the resource assert file_contents =~ - ~S{create unique_index(:posts, [:title], name: "posts_title_index", prefix: "example")} + ~S{create unique_index(:posts, ["title"], name: "posts_title_index", prefix: "example")} end end @@ -703,18 +715,18 @@ defmodule AshPostgres.MigrationGeneratorTest do file1_content = File.read!(file1) assert file1_content =~ - "create unique_index(:posts, [:title], name: \"posts_title_index\")" + "create unique_index(:posts, [\"title\"], name: \"posts_title_index\")" file2_content = File.read!(file2) assert file2_content =~ - "drop_if_exists unique_index(:posts, [:title], name: \"posts_title_index\")" + "drop_if_exists unique_index(:posts, [\"title\"], name: \"posts_title_index\")" assert file2_content =~ - "create unique_index(:posts, [:name], name: \"posts_unique_name_index\")" + "create unique_index(:posts, [\"name\"], name: \"posts_unique_name_index\")" assert file2_content =~ - "create unique_index(:posts, [:title], name: \"posts_unique_title_index\")" + "create unique_index(:posts, [\"title\"], name: \"posts_unique_title_index\")" end test "when an attribute exists only on some of the resources that use the same table, it isn't marked as null: false" do @@ -1073,7 +1085,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") assert File.read!(file) =~ - ~S{create unique_index(:users, [:name], name: "users_unique_name_index")} + ~S{create unique_index(:users, ["name"], name: "users_unique_name_index")} end test "when modified, the foreign key is dropped before modification" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 511857ff..467b2845 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -60,6 +60,9 @@ defmodule AshPostgres.Test.Post do repo(AshPostgres.TestRepo) base_filter_sql("type = 'sponsored'") + calculations_to_sql(upper_thing: "UPPER(uniq_on_upper)") + identity_wheres_to_sql(uniq_if_contains_foo: "(uniq_if_contains_foo LIKE '%foo%')") + check_constraints do check_constraint(:price, "price_must_be_positive", message: "yo, bad price", @@ -186,6 +189,11 @@ defmodule AshPostgres.Test.Post do identities do identity(:uniq_one_and_two, [:uniq_one, :uniq_two]) + identity(:uniq_on_upper, [:upper_thing]) + + identity(:uniq_if_contains_foo, [:uniq_if_contains_foo]) do + where expr(contains(title, "foo")) + end end attributes do @@ -219,6 +227,8 @@ defmodule AshPostgres.Test.Post do attribute(:uniq_two, :string, public?: true) attribute(:uniq_custom_one, :string, public?: true) attribute(:uniq_custom_two, :string, public?: true) + attribute(:uniq_on_upper, :string, public?: true) + attribute(:uniq_if_contains_foo, :string, public?: true) attribute :list_containing_nils, {:array, :string} do public?(true) @@ -335,6 +345,8 @@ defmodule AshPostgres.Test.Post do end calculations do + calculate(:upper_thing, :string, expr(fragment("UPPER(?)", uniq_on_upper))) + calculate( :author_has_post_with_follower_named_fred, :boolean, From 633c00c2e9780e1add76f322cb7f07d66024a351 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 24 May 2024 01:15:50 -0400 Subject: [PATCH 0459/1215] chore: release version v2.0.5 --- CHANGELOG.md | 6 ++++++ mix.exs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 531ba070..508775cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.5](https://github.com/ash-project/ash_postgres/compare/v2.0.4...v2.0.5) (2024-05-24) + +### Improvements: + +- [idenities] update `ash` and support new `identity` features + ## [v2.0.4](https://github.com/ash-project/ash_postgres/compare/v2.0.3...v2.0.4) (2024-05-23) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 3920eb45..7e49974b 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.4" + @version "2.0.5" def project do [ From 06752d37e42bcd41492ebbea127e53ee181159fd Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Mon, 27 May 2024 16:45:02 +0300 Subject: [PATCH 0460/1215] improvement: add `ash_postgres.squash_snapshots` mix task (#302) --- .../migration_generator.ex | 20 +- .../tasks/ash_postgres.squash_snapshots.ex | 117 ++++++++++ test/mix_squash_snapshots_test.exs | 205 ++++++++++++++++++ 3 files changed, 326 insertions(+), 16 deletions(-) create mode 100644 lib/mix/tasks/ash_postgres.squash_snapshots.ex create mode 100644 test/mix_squash_snapshots_test.exs diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index eefe3744..c141fcdb 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2322,26 +2322,14 @@ defmodule AshPostgres.MigrationGenerator do if File.exists?(snapshot_folder) do snapshot_folder |> File.ls!() - |> Enum.filter(&String.ends_with?(&1, ".json")) - |> Enum.map(&String.trim_trailing(&1, ".json")) - |> Enum.map(&Integer.parse/1) - |> Enum.filter(fn - {_int, remaining} -> - remaining == "" - - :error -> - false - end) - |> Enum.map(&elem(&1, 0)) + |> Enum.filter(&String.match?(&1, ~r/^\d{14}\.json$/)) |> case do [] -> get_old_snapshot(folder, snapshot) - timestamps -> - timestamp = Enum.max(timestamps) - snapshot_file = Path.join(snapshot_folder, "#{timestamp}.json") - - snapshot_file + snapshot_files -> + snapshot_folder + |> Path.join(Enum.max(snapshot_files)) |> File.read!() |> load_snapshot() end diff --git a/lib/mix/tasks/ash_postgres.squash_snapshots.ex b/lib/mix/tasks/ash_postgres.squash_snapshots.ex new file mode 100644 index 00000000..1cf1fd68 --- /dev/null +++ b/lib/mix/tasks/ash_postgres.squash_snapshots.ex @@ -0,0 +1,117 @@ +defmodule Mix.Tasks.AshPostgres.SquashSnapshots do + use Mix.Task + + @shortdoc "Cleans snapshots folder, leaving only one snapshot per resource" + + @switches [ + into: :string, + snapshot_path: :string, + quiet: :boolean, + dry_run: :boolean, + check: :boolean + ] + + @moduledoc """ + Cleans snapshots folder, leaving only one snapshot per resource. + + ## Examples + + mix ash_postgres.squash_snapshots + mix ash_postgres.squash_snapshots --check --quiet + mix ash_postgres.squash_snapshots --into zero + mix ash_postgres.squash_snapshots --dry-run + + ## Command line options + + * `--into` - + `last`, `first` or `zero`. The default is `last`. Determines which name to use for + a remaining snapshot. `last` keeps the name of the last snapshot, `first` renames it to the previously first, + `zero` sets name with fourteen zeros. + * `--snapshot-path` - a custom path to stored snapshots. The default is "priv/resource_snapshots". + * `--quiet` - no messages will not be printed. + * `--dry-run` - no files are touched, instead prints folders that have snapshots to squash. + * `--check` - no files are touched, instead returns an exit(1) code if there are snapshots to squash. + """ + + def run(args) do + {opts, []} = OptionParser.parse!(args, strict: @switches) + + opts = + opts + |> Map.new() + |> Map.put_new(:into, "last") + |> Map.put_new(:snapshot_path, "priv/resource_snapshots") + |> Map.put_new(:quiet, false) + |> Map.put_new(:dry_run, false) + |> Map.put_new(:check, false) + |> Map.update!(:into, fn + "last" -> :last + "first" -> :first + "zero" -> :zero + _other -> raise "Valid values for --into flag are `last`, `first` and `zero`." + end) + + squashable = + opts.snapshot_path + |> Path.join("**/*.json") + |> Path.wildcard() + |> Enum.filter(&String.match?(Path.basename(&1), ~r/^\d{14}\.json$/)) + |> Enum.group_by(&Path.dirname(&1)) + |> Enum.reduce([], fn {folder, snapshots}, squashable -> + last_snapshot = Enum.max(snapshots) + + into_snapshot = + case opts.into do + :last -> last_snapshot + :first -> Enum.min(snapshots) + :zero -> Path.join(folder, "00000000000000.json") + end + + if length(snapshots) > 1 or last_snapshot != into_snapshot do + [{folder, snapshots, last_snapshot, into_snapshot} | squashable] + else + squashable + end + end) + |> Enum.reverse() + + if Enum.empty?(squashable) do + print(opts, "No snapshots to squash.") + else + if opts.dry_run do + print(opts, "Snapshots in following folders would have been squashed in non-dry run:") + print(opts, Enum.map_join(squashable, "\n", fn {folder, _, _, _} -> folder end)) + + if opts.check do + exit({:shutdown, 1}) + end + end + + if opts.check do + print(opts, """ + Snapshots would have been squashed, but the --check flag was provided. + + To see what snapshots would have been squashed, run with the --dry-run + flag. To squash those snapshots, run without either flag. + """) + + exit({:shutdown, 1}) + end + + if not opts.dry_run do + for {_folder, snapshots, last_snapshot, into_snapshot} <- squashable do + for snapshot <- snapshots, snapshot != last_snapshot do + File.rm!(snapshot) + end + + if last_snapshot != into_snapshot do + File.rename!(last_snapshot, into_snapshot) + end + end + end + end + end + + defp print(%{quiet: true}, _message), do: nil + defp print(_opts, message), do: Mix.shell().info(message) +end diff --git a/test/mix_squash_snapshots_test.exs b/test/mix_squash_snapshots_test.exs new file mode 100644 index 00000000..b9ac91e6 --- /dev/null +++ b/test/mix_squash_snapshots_test.exs @@ -0,0 +1,205 @@ +defmodule AshPostgres.MixSquashSnapshotsTest do + use AshPostgres.RepoCase, async: false + + defmacrop defposts(mod \\ Post, do: body) do + quote do + Code.compiler_options(ignore_module_conflict: true) + + defmodule unquote(mod) do + use Ash.Resource, + domain: nil, + data_layer: AshPostgres.DataLayer + + postgres do + table "posts" + repo(AshPostgres.TestRepo) + + custom_indexes do + # need one without any opts + index(["id"]) + index(["id"], unique: true, name: "test_unique_index") + end + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + unquote(body) + end + + Code.compiler_options(ignore_module_conflict: false) + end + end + + defmacrop defdomain(resources) do + quote do + Code.compiler_options(ignore_module_conflict: true) + + defmodule Domain do + use Ash.Domain + + resources do + for resource <- unquote(resources) do + resource(resource) + end + end + end + + Code.compiler_options(ignore_module_conflict: false) + end + end + + def squash_snapshots(args) do + args = ["--snapshot-path", "test_snapshots_path"] ++ args + Mix.Task.rerun("ash_postgres.squash_snapshots", args) + end + + def list_snapshots do + Path.wildcard("test_snapshots_path/**/[0-9]*.json") + end + + describe "with two snapshots to squash" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + + Mix.shell(Mix.Shell.Process) + + defposts do + identities do + identity(:title, [:title]) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + defposts do + identities do + identity(:title, [:title]) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + attribute(:name, :string, allow_nil?: false, public?: true) + end + end + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + :ok + end + + test "runs without flags" do + [_first_snapshot, last_snapshot] = list_snapshots() |> Enum.sort() + squash_snapshots([]) + assert [^last_snapshot] = list_snapshots() + end + + test "runs with `--check`" do + prev_snapshots = list_snapshots() + assert catch_exit(squash_snapshots(["--check"])) == {:shutdown, 1} + assert prev_snapshots == list_snapshots() + end + + test "runs with `--dry-run`" do + prev_snapshots = list_snapshots() + squash_snapshots(["--dry-run"]) + assert prev_snapshots == list_snapshots() + end + + test "runs with `--into last`" do + [_first_snapshot, last_snapshot] = list_snapshots() |> Enum.sort() + squash_snapshots(["--into", "last"]) + assert [^last_snapshot] = list_snapshots() + end + + test "runs with `--into first`" do + [first_snapshot, _last_snapshot] = list_snapshots() |> Enum.sort() + squash_snapshots(["--into", "first"]) + assert [^first_snapshot] = list_snapshots() + end + + test "runs with `--into zero`" do + squash_snapshots(["--into", "zero"]) + assert ["test_snapshots_path/test_repo/posts/00000000000000.json"] = list_snapshots() + end + end + + describe "with one snapshot to squash" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + + Mix.shell(Mix.Shell.Process) + + defposts do + identities do + identity(:title, [:title]) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + :ok + end + + test "runs with `--check`" do + prev_snapshots = list_snapshots() + squash_snapshots(["--check"]) + assert prev_snapshots == list_snapshots() + end + + test "runs with `--check --into last`" do + prev_snapshots = list_snapshots() + squash_snapshots(["--check", "--into", "last"]) + assert prev_snapshots == list_snapshots() + end + + test "runs with `--check --into first`" do + prev_snapshots = list_snapshots() + squash_snapshots(["--check", "--into", "last"]) + assert prev_snapshots == list_snapshots() + end + + test "runs with `--check --into zero`" do + prev_snapshots = list_snapshots() + assert catch_exit(squash_snapshots(["--check", "--into", "zero"])) == {:shutdown, 1} + assert prev_snapshots == list_snapshots() + end + end +end From 33cfbd1cc4fe7b7ea1fb71132dfa6d49875c1ef8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 27 May 2024 11:43:17 -0400 Subject: [PATCH 0461/1215] chore: drop columns by default, change CLI option to `dont-drop-columns` closes #307 --- lib/migration_generator/migration_generator.ex | 6 +++--- lib/mix/tasks/ash_postgres.generate_migrations.ex | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index c141fcdb..0aa7399e 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -18,7 +18,7 @@ defmodule AshPostgres.MigrationGenerator do format: true, dry_run: false, check: false, - drop_columns: false + dont_drop_columns: false def generate(domains, opts \\ []) do domains = List.wrap(domains) @@ -1984,7 +1984,7 @@ defmodule AshPostgres.MigrationGenerator do %Operation.RemovePrimaryKey{schema: snapshot.schema, table: snapshot.table}, must_drop_pkey? && drop_in_down? && %Operation.RemovePrimaryKeyDown{ - commented?: !opts.drop_columns && drop_in_down_commented?, + commented?: opts.dont_drop_columns && drop_in_down_commented?, schema: snapshot.schema, table: snapshot.table } @@ -2189,7 +2189,7 @@ defmodule AshPostgres.MigrationGenerator do attribute: attribute, table: snapshot.table, schema: snapshot.schema, - commented?: !opts.drop_columns + commented?: opts.dont_drop_columns } end) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 4fd67b61..5348d27a 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -38,9 +38,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do won't be able to roll back, because the data has been deleted. In a rolling restart situation, some of the machines/pods/whatever may still be running after the column has been deleted, causing errors. With this in mind, its best not to delete those columns until later, after the data has been confirmed unnecessary. - To that end, the migration generator leaves the column dropping code commented. You can pass `--drop_columns` - to tell it to uncomment those statements. Additionally, you can just uncomment that code on a case by case - basis. + To that end, you can pass `--dont-drop-columns` to tell it to comment out those statements. #### Conflicts/Multiple Resources @@ -96,7 +94,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do no_format: :boolean, dry_run: :boolean, check: :boolean, - drop_columns: :boolean + dont_drop_columns: :boolean ] ) From 4fbd821ee3eaec96ef859d36f8f7ddd28716d3ed Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 27 May 2024 12:15:42 -0400 Subject: [PATCH 0462/1215] fix: support old/new parameterized type format closes #308 --- lib/sql_implementation.ex | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index bd43e470..57e973ec 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -129,6 +129,10 @@ defmodule AshPostgres.SqlImplementation do @impl true def parameterized_type(type, constraints, no_maps? \\ true) + def parameterized_type({:parameterized, _} = type, _, _) do + type + end + def parameterized_type({:parameterized, _, _} = type, _, _) do type end @@ -192,7 +196,7 @@ defmodule AshPostgres.SqlImplementation do type end - {:parameterized, type, constraints || []} + Ecto.ParameterizedType.init(type, constraints || []) else type end @@ -337,7 +341,7 @@ defmodule AshPostgres.SqlImplementation do else type = if is_atom(type) && :erlang.function_exported(type, :type, 1) do - {:parameterized, type, []} |> array_to_in() + Ecto.ParameterizedType.init(type, []) |> array_to_in() else type |> array_to_in() end @@ -357,7 +361,7 @@ defmodule AshPostgres.SqlImplementation do else type = if is_atom(type) && :erlang.function_exported(type, :type, 1) do - {:parameterized, type, []} |> array_to_in() + Ecto.ParameterizedType.init(type, []) |> array_to_in() else type |> array_to_in() end @@ -370,9 +374,6 @@ defmodule AshPostgres.SqlImplementation do defp array_to_in({:array, v}), do: {:in, array_to_in(v)} - defp array_to_in({:parameterized, type, constraints}), - do: {:parameterized, array_to_in(type), constraints} - defp array_to_in(v), do: v defp vagueness({:in, type}), do: vagueness(type) From 284928e5c1ee36ecc4ca79307aedffbb2f6525be Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 28 May 2024 00:08:34 -0400 Subject: [PATCH 0463/1215] chore: always reselect primary key on updates --- lib/data_layer.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 10839a4b..c5de23ab 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2615,7 +2615,10 @@ defmodule AshPostgres.DataLayer do {:error, error} {:ok, query} -> - modifying = Map.keys(changeset.attributes) ++ Keyword.keys(changeset.atomics) + modifying = + Map.keys(changeset.attributes) ++ + Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) + query = Ecto.Query.select(query, ^modifying) try do From f8916c14ca1a27ae5217522d4e2f424fc99c06c6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 28 May 2024 13:32:02 -0400 Subject: [PATCH 0464/1215] improvement: require clarification of index names > 63 characters closes #304 --- lib/data_layer.ex | 3 +- .../validate_identity_index_names.ex | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 lib/verifiers/validate_identity_index_names.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c5de23ab..55ddf14a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -407,7 +407,8 @@ defmodule AshPostgres.DataLayer do AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates, AshPostgres.Verifiers.ValidateReferences, AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType, - AshPostgres.Verifiers.EnsureTableOrPolymorphic + AshPostgres.Verifiers.EnsureTableOrPolymorphic, + AshPostgres.Verifiers.ValidateIdentityIndexNames ] def migrate(args) do diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex new file mode 100644 index 00000000..235389af --- /dev/null +++ b/lib/verifiers/validate_identity_index_names.ex @@ -0,0 +1,61 @@ +defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do + @moduledoc false + use Spark.Dsl.Verifier + alias Spark.Dsl.Verifier + + def verify(dsl) do + identity_index_names = + AshPostgres.DataLayer.Info.identity_index_names(dsl) + + Enum.each(identity_index_names, fn {identity, name} -> + if String.length(name) > 63 do + raise Spark.Error.DslError, + path: [:postgres, :identity_index_names, identity], + module: Verifier.get_persisted(dsl, :module), + message: """ + Identity #{identity} has a name that is too long. Names must be 63 characters or less. + """ + end + end) + + table = AshPostgres.DataLayer.Info.table(dsl) + + if table do + dsl + |> Ash.Resource.Info.identities() + |> Enum.map(fn identity -> + {identity, identity_index_names[identity.name] || "#{table}_#{identity.name}_index"} + end) + |> Enum.group_by(&elem(&1, 1)) + |> Enum.each(fn + {name, [_, _ | _] = identities} -> + raise Spark.Error.DslError, + path: [:postgres, :identity_index_names, name], + module: Verifier.get_persisted(dsl, :module), + message: """ + Multiple identities would result in the same index name: #{name} + + Identities: #{inspect(Enum.map(identities, & &1.name))} + """ + + {name, [identity]} -> + if String.length(name) > 63 do + raise Spark.Error.DslError, + path: [:postgres, :identity_index_names, name], + module: Verifier.get_persisted(dsl, :module), + message: """ + Identity #{identity.name} has a name that is too long. Names must be 63 characters or less. + + Please configure an index name for this identity in the `identity_index_names` configuration. For example:application + + postgres do + identity_index_names #{inspect(identity.name)}: "a_shorter_name" + end + """ + end + end) + end + + :ok + end +end From 9f1936aef4b65ac1699187ef366ee1f8a67de175 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 28 May 2024 13:52:53 -0400 Subject: [PATCH 0465/1215] fix: ensure that identities are dropped when where/nils_distinct? are changed fix: ensure that `where` is wrapped in parenthesis --- .../migration_generator.ex | 4 +- lib/migration_generator/operation.ex | 9 ++++- test/migration_generator_test.exs | 38 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 0aa7399e..76ab35c2 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1762,7 +1762,9 @@ defmodule AshPostgres.MigrationGenerator do identity.name == old_identity.name && Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && old_identity.base_filter == identity.base_filter && - old_identity.all_tenants? == identity.all_tenants? + old_identity.all_tenants? == identity.all_tenants? && + old_identity.nils_distinct? == identity.nils_distinct? && + old_identity.where == identity.where end) end) end diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 97f225b2..e57629e7 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -822,10 +822,17 @@ defmodule AshPostgres.MigrationGenerator.Operation do "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?), option("where", where)])})" base_filter -> + base_filter = "(#{base_filter})" + "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], where: \"#{base_filter}\", #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?)])})" - true -> + where -> + where = "(#{where})" + "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?), option("where", where)])})" + + true -> + "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option("prefix", schema), option("nulls_distinct", nils_distinct?)])})" end end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 596f2d55..31151227 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -504,6 +504,44 @@ defmodule AshPostgres.MigrationGeneratorTest do ~S[ALTER INDEX posts_title_index RENAME TO titles_r_unique_dawg] end + test "when changing the where clause, it is properly dropped and recreated" do + defposts do + postgres do + identity_wheres_to_sql(title: "title != 'fred' and title != 'george'") + end + + identities do + identity(:title, [:title], where: expr(title not in ["fred", "george"])) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + + assert [file_before, _] = + String.split( + File.read!(file2), + ~S{create unique_index(:posts, ["title"], name: "posts_title_index", where: "(title != 'fred' and title != 'george')")} + ) + + assert file_before =~ + ~S{drop_if_exists unique_index(:posts, ["title"], name: "posts_title_index")} + end + test "when adding a field, it adds the field" do defposts do identities do From d3b2e96b7b1704049ea044c7411b5475194d4c45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 07:37:17 -0400 Subject: [PATCH 0466/1215] chore(deps): bump ash from 3.0.7 to 3.0.8 (#310) --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index bd556f8a..18506611 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.7", "6c37e092f53b1b21eb89596f600a652b2a601f84378f44fd5dd1cdec72eb1cc2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9288ddb50fe727096c6f63fd82c631de2505dcd29bdfa50b5dc13c865f0bf434"}, + "ash": {:hex, :ash, "3.0.8", "e84a0707205e2a1ed16e9c1acaf32e08658bf4a36cba460eefaf79fedf92abd7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "92436ab7c465d8a8706383cb9cfd9fbf074d4bd8632b86895a6e6bf3b9eee2cd"}, "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -27,14 +27,14 @@ "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, - "reactor": {:hex, :reactor, "0.8.2", "b2be82b1c3402537d06a8f85bb1849f72cb6b4be140495cb8956de7aec2fdebd", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c35eb23b77cc77ba922af108722ac93257899e35cfdd18882f0e659ad2cac9f3"}, + "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"}, "spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "1.0.0", "c1380747a4650902732696861d5cb66ad3cb1cc93f31c2c8498bf87cddbabe2d", [:mix], [], "hexpm", "acd53e27c66c617d466f42ec77a7f59e5751f6051583c621ccdb055b9690435d"}, + "stream_data": {:hex, :stream_data, "1.1.0", "ef3a7cac0f200c43caf3e6caf9be63115851b4f1cde3f21afaab220adc40e3d7", [:mix], [], "hexpm", "cccc411d5facf1bab86e7c671382d164f05f8992574c95349d3c8b317e14d953"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, From ff47ff0e0699c4cff94cb963bd4a497fc8005f8a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 May 2024 12:29:34 -0400 Subject: [PATCH 0467/1215] fix: properly support aggregate references in atomic updates (yes, you read that right) --- lib/data_layer.ex | 243 ++++++++++++++++++------------ mix.exs | 2 +- mix.lock | 2 +- test/atomics_test.exs | 17 +++ test/migration_generator_test.exs | 2 +- test/support/resources/post.ex | 4 + 6 files changed, 174 insertions(+), 96 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 55ddf14a..def29a87 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1433,98 +1433,7 @@ defmodule AshPostgres.DataLayer do end defp bulk_updatable_query(query, resource, atomics, calculations, context, type \\ :update) do - requires_adding_inner_join? = - case type do - :update -> - # could potentially optimize this to avoid the subquery by shuffling free - # inner joins to the top of the query - has_inner_join_to_start? = - case Enum.at(query.joins, 0) do - nil -> - false - - %{qual: :inner} -> - true - - _ -> - false - end - - cond do - has_inner_join_to_start? -> - false - - Enum.any?(query.joins, &(&1.qual != :inner)) -> - true - - Enum.any?(atomics ++ calculations, fn {_, expr} -> - Ash.Filter.list_refs(expr) |> Enum.any?(&(&1.relationship_path != [])) - end) -> - true - - true -> - false - end - - :destroy -> - Enum.any?(query.joins, &(&1.qual != :inner)) || - Enum.any?(atomics ++ calculations, fn {_, expr} -> - expr |> Ash.Filter.list_refs() |> Enum.any?(&(&1.relationship_path != [])) - end) - end - - needs_to_join? = - requires_adding_inner_join? || - query.limit || query.offset - - query = - if needs_to_join? do - root_query = Ecto.Query.exclude(query, :select) - - root_query = - cond do - query.limit || query.offset -> - from(row in Ecto.Query.subquery(root_query), []) - - !Enum.empty?(query.joins) -> - from(row in Ecto.Query.subquery(Ecto.Query.exclude(root_query, :order_by)), []) - - true -> - Ecto.Query.exclude(root_query, :order_by) - end - - dynamic = - Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> - if dynamic do - Ecto.Query.dynamic( - [row, joining], - field(row, ^pkey) == field(joining, ^pkey) and ^dynamic - ) - else - Ecto.Query.dynamic([row, joining], field(row, ^pkey) == field(joining, ^pkey)) - end - end) - - faked_query = - from(row in query.from.source, - inner_join: limiter in ^root_query, - as: ^0, - on: ^dynamic - ) - |> AshSql.Bindings.default_bindings( - query.__ash_bindings__.resource, - AshPostgres.SqlImplementation, - context - ) - - faked_query - else - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.exclude(:order_by) - end - - Enum.reduce_while(atomics ++ calculations, {:ok, query}, fn {_, expr}, {:ok, query} -> + Enum.reduce_while(atomics, {:ok, query}, fn {_, expr}, {:ok, query} -> used_aggregates = Ash.Filter.used_aggregates(expr, []) @@ -1545,6 +1454,154 @@ defmodule AshPostgres.DataLayer do {:halt, {:error, error}} end end) + |> case do + {:ok, query} -> + requires_adding_inner_join? = + case type do + :update -> + # could potentially optimize this to avoid the subquery by shuffling free + # inner joins to the top of the query + has_inner_join_to_start? = + case Enum.at(query.joins, 0) do + nil -> + false + + %{qual: :inner} -> + true + + _ -> + false + end + + cond do + has_inner_join_to_start? -> + false + + Enum.any?(query.joins, &(&1.qual != :inner)) -> + true + + Enum.any?(atomics ++ calculations, fn {_, expr} -> + Ash.Filter.list_refs(expr) |> Enum.any?(&(&1.relationship_path != [])) + end) -> + true + + true -> + false + end + + :destroy -> + Enum.any?(query.joins, &(&1.qual != :inner)) || + Enum.any?(atomics ++ calculations, fn {_, expr} -> + expr |> Ash.Filter.list_refs() |> Enum.any?(&(&1.relationship_path != [])) + end) + end + + needs_to_join? = + requires_adding_inner_join? || + query.limit || query.offset + + query = + if needs_to_join? do + root_query = Ecto.Query.exclude(query, :select) + + root_query_result = + cond do + query.limit || query.offset -> + with {:ok, root_query} <- + AshSql.Atomics.select_atomics(resource, root_query, atomics) do + {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} + end + + !Enum.empty?(query.joins) -> + with root_query <- Ecto.Query.exclude(root_query, :order_by), + {:ok, root_query} <- + AshSql.Atomics.select_atomics(resource, root_query, atomics) do + {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} + end + + true -> + {:ok, Ecto.Query.exclude(root_query, :order_by), false} + end + + case root_query_result do + {:ok, root_query, selected_atomics?} -> + dynamic = + Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> + if dynamic do + Ecto.Query.dynamic( + [row, joining], + field(row, ^pkey) == field(joining, ^pkey) and ^dynamic + ) + else + Ecto.Query.dynamic( + [row, joining], + field(row, ^pkey) == field(joining, ^pkey) + ) + end + end) + + faked_query = + from(row in query.from.source, + inner_join: limiter in ^root_query, + as: ^0, + on: ^dynamic + ) + |> AshSql.Bindings.default_bindings( + query.__ash_bindings__.resource, + AshPostgres.SqlImplementation, + context + ) + |> then(fn query -> + if selected_atomics? do + Map.update!(query, :__ash_bindings__, &Map.put(&1, :atomics_in_binding, 0)) + else + query + end + end) + + {:ok, faked_query} + + {:error, error} -> + {:error, error} + end + else + {:ok, + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.exclude(:order_by)} + end + + case query do + {:ok, query} -> + Enum.reduce_while(calculations, {:ok, query}, fn {_, expr}, {:ok, query} -> + used_aggregates = + Ash.Filter.used_aggregates(expr, []) + + with {:ok, query} <- + AshSql.Join.join_all_relationships( + query, + %Ash.Filter{ + resource: resource, + expression: expr + }, + left_only?: true + ), + {:ok, query} <- + AshSql.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do + {:cont, {:ok, query}} + else + {:error, error} -> + {:halt, {:error, error}} + end + end) + + {:error, error} -> + {:error, error} + end + + {:error, error} -> + {:error, error} + end end @impl true @@ -1563,7 +1620,7 @@ defmodule AshPostgres.DataLayer do case bulk_updatable_query( query, resource, - changeset.atomics, + [], options[:calculations] || [], changeset.context, :destroy diff --git a/mix.exs b/mix.exs index 7e49974b..54a907ac 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0 and >= 3.0.7")}, - {:ash_sql, ash_sql_version("~> 0.1 and >= 0.1.3")}, + {:ash_sql, ash_sql_version("~> 0.2")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 18506611..97b61cd6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.8", "e84a0707205e2a1ed16e9c1acaf32e08658bf4a36cba460eefaf79fedf92abd7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "92436ab7c465d8a8706383cb9cfd9fbf074d4bd8632b86895a6e6bf3b9eee2cd"}, - "ash_sql": {:hex, :ash_sql, "0.1.3", "c9acc4809b7f253aad31764024aee0cd632077a32cff6bea3b105c7b8d9015b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d2d3d1044f0fa48454d0cdaeb22d55a2de3210d48a2208fd2eecf6f3007a5216"}, + "ash_sql": {:hex, :ash_sql, "0.2.0", "9a80af47d31e0e0f0c8596fadb4daeb3ea322d00de710b12006137f9c7bee859", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bc8997b6fdf52a0144c17969aef88bd2dc22958c8d1b1c18fbcfb4bec3b849f1"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index a1221ae4..845c8d70 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -78,6 +78,23 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "an atomic update can be set to the value of an aggregate" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "bar", author_id: author.id}) + |> Ash.create!() + + # just asserting that there is no exception here + post + |> Ash.Changeset.for_update(:set_title_to_sum_of_author_count_of_posts) + |> Ash.update!() + end + test "an atomic validation is based on where it appears in the action" do post = Post diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 31151227..bcbb59a5 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -241,7 +241,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file_contents =~ ~S{create index(:posts, ["id"]} assert file_contents =~ - ~S{create unique_index(:posts, ["second_title"], name: "posts_second_title_index", prefix: "example", nulls_distinct: false, where: "(second_title like '%foo%')")} + ~S{create unique_index(:posts, ["second_title"], name: "posts_second_title_index", prefix: "example", nulls_distinct: false, where: "((second_title like '%foo%'))")} # the migration adds the id, with its default assert file_contents =~ diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 467b2845..65017507 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -96,6 +96,10 @@ defmodule AshPostgres.Test.Post do change(filter(expr(title == "fred"))) end + update :set_title_to_sum_of_author_count_of_posts do + change(atomic_update(:title, expr("#{sum_of_author_count_of_posts}"))) + end + destroy :destroy_with_confirm do require_atomic?(false) argument(:confirm, :string, allow_nil?: false) From 93a536043c6a50603f2d43a44d1cc4487385a670 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 May 2024 12:32:23 -0400 Subject: [PATCH 0468/1215] chore: release version v2.0.6 --- CHANGELOG.md | 18 ++++++++++++++++++ mix.exs | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508775cc..7fa2fe57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.6](https://github.com/ash-project/ash_postgres/compare/v2.0.5...v2.0.6) (2024-05-29) + +### Bug Fixes: + +- [atomic updates] properly support aggregate references in atomic updates + +- [migration generator] ensure that identities are dropped when where/nils_distinct? are changed + +- [migration generator] ensure that `where` is wrapped in parenthesis + +- [ecto compatibility] support old/new parameterized type format + +### Improvements: + +- [identities] require clarification of index names > 63 characters + +- [mix ash_postgres.squash_snapshots] add `ash_postgres.squash_snapshots` mix task (#302) + ## [v2.0.5](https://github.com/ash-project/ash_postgres/compare/v2.0.4...v2.0.5) (2024-05-24) ### Improvements: diff --git a/mix.exs b/mix.exs index 54a907ac..fa4745c1 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.5" + @version "2.0.6" def project do [ From bc46d9d9c4b01cef85a553c2423ea2ddd2c5684e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 30 May 2024 00:27:20 -0500 Subject: [PATCH 0469/1215] test: add test reproducing related aggregate reference --- test/atomics_test.exs | 17 +++++++++++++++++ test/support/resources/post.ex | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 845c8d70..5a83521f 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -95,6 +95,23 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "an atomic update can be set to the value of a related aggregate" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "bar", author_id: author.id}) + |> Ash.create!() + + # just asserting that there is no exception here + post + |> Ash.Changeset.for_update(:set_title_to_author_profile_description) + |> Ash.update!() + end + test "an atomic validation is based on where it appears in the action" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 65017507..23376ab0 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -100,6 +100,10 @@ defmodule AshPostgres.Test.Post do change(atomic_update(:title, expr("#{sum_of_author_count_of_posts}"))) end + update :set_title_to_author_profile_description do + change(atomic_update(:title, expr(author.profile_description))) + end + destroy :destroy_with_confirm do require_atomic?(false) argument(:confirm, :string, allow_nil?: false) From 89da92bcd1d0688110eb84a0ff36cfedbc955006 Mon Sep 17 00:00:00 2001 From: Mikael Muszynski Date: Sun, 2 Jun 2024 18:34:47 +0200 Subject: [PATCH 0470/1215] docs: Remove excess space in get-started-with-ash-postgres.md (#315) --- documentation/tutorials/get-started-with-ash-postgres.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index 5c3326ee..5897b4ff 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -318,7 +318,7 @@ For example, we can determine the percentage of tickets that are open: # in lib/helpdesk/support/representative.ex calculations do - calculate :percent_open, :float, expr(open_tickets / total_tickets ) + calculate :percent_open, :float, expr(open_tickets / total_tickets) end ``` From cf9f6ec60ae704942b6efef55fd1c8512495c401 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 30 May 2024 00:27:20 -0500 Subject: [PATCH 0471/1215] test: add test reproducing related aggregate reference --- lib/data_layer.ex | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index def29a87..6c1a4653 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1509,6 +1509,17 @@ defmodule AshPostgres.DataLayer do query.limit || query.offset -> with {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do + remaining_to_select = + Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) -- + Keyword.keys(atomics) + + root_query = + Ecto.Query.select_merge( + root_query, + [record], + map(record, ^remaining_to_select) + ) + {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} end @@ -1516,6 +1527,17 @@ defmodule AshPostgres.DataLayer do with root_query <- Ecto.Query.exclude(root_query, :order_by), {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do + remaining_to_select = + Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) -- + Keyword.keys(atomics) + + root_query = + Ecto.Query.select_merge( + root_query, + [record], + map(record, ^remaining_to_select) + ) + {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} end From df250964e0723310036122ce6b8d0752542e2216 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 2 Jun 2024 12:34:02 -0400 Subject: [PATCH 0472/1215] fix: ensure that all current attribute values are selected on bulk update shifted root query closes #314 --- lib/data_layer.ex | 14 ++++++-------- mix.exs | 2 +- mix.lock | 4 ++-- test/bulk_update_test.exs | 2 +- test/support/resources/post.ex | 6 +++++- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6c1a4653..cdf5bb71 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1509,15 +1509,14 @@ defmodule AshPostgres.DataLayer do query.limit || query.offset -> with {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do - remaining_to_select = - Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) -- - Keyword.keys(atomics) + select = + Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) root_query = Ecto.Query.select_merge( root_query, [record], - map(record, ^remaining_to_select) + map(record, ^select) ) {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} @@ -1527,15 +1526,14 @@ defmodule AshPostgres.DataLayer do with root_query <- Ecto.Query.exclude(root_query, :order_by), {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do - remaining_to_select = - Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) -- - Keyword.keys(atomics) + select = + Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) root_query = Ecto.Query.select_merge( root_query, [record], - map(record, ^remaining_to_select) + map(record, ^select) ) {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} diff --git a/mix.exs b/mix.exs index fa4745c1..67ccb432 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0 and >= 3.0.7")}, - {:ash_sql, ash_sql_version("~> 0.2")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.1")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 97b61cd6..adb0f07a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.0.8", "e84a0707205e2a1ed16e9c1acaf32e08658bf4a36cba460eefaf79fedf92abd7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "92436ab7c465d8a8706383cb9cfd9fbf074d4bd8632b86895a6e6bf3b9eee2cd"}, - "ash_sql": {:hex, :ash_sql, "0.2.0", "9a80af47d31e0e0f0c8596fadb4daeb3ea322d00de710b12006137f9c7bee859", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bc8997b6fdf52a0144c17969aef88bd2dc22958c8d1b1c18fbcfb4bec3b849f1"}, + "ash": {:hex, :ash, "3.0.9", "f98266488f9f152130a6015c7158f70ba8262939c4dd0728bb55ab48b6d282eb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "adb0853558c13cd77a4489e755df149e0e43bde9c069811ec244dd5dcbe62cd0"}, + "ash_sql": {:hex, :ash_sql, "0.2.1", "a1b10af77e1eb4e038cce7cbbd7cdf38c9982fba07587838f30fab7761596786", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1c0ecc87ce6ebcaa5b712f8d832e2e1845ab3330f86a377636e92d9e85f064f0"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 44a91960..e0bb1737 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -147,7 +147,7 @@ defmodule AshPostgres.BulkUpdateTest do Post |> Ash.Query.limit(1) |> Ash.Query.sort(:title) - |> Ash.bulk_update!(:update, %{}, + |> Ash.bulk_update!(:dont_validate, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}, strategy: :atomic ) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 23376ab0..7f8781e7 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -139,6 +139,8 @@ defmodule AshPostgres.Test.Post do require_atomic?(false) end + update(:dont_validate) + update :change_title_to_foo_unless_its_already_foo do validate(attribute_does_not_equal(:title, "foo")) change(set_attribute(:title, "foo")) @@ -349,7 +351,9 @@ defmodule AshPostgres.Test.Post do end validations do - validate(attribute_does_not_equal(:title, "not allowed")) + validate(attribute_does_not_equal(:title, "not allowed"), + where: [negate(action_is(:dont_validate))] + ) end calculations do From 74b0819e04217099db41f9b0933e573802521991 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 08:19:19 -0400 Subject: [PATCH 0473/1215] chore(deps): bump ash_sql from 0.2.1 to 0.2.2 (#317) Bumps [ash_sql](https://github.com/ash-project/ash_sql) from 0.2.1 to 0.2.2. - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.1...v0.2.2) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index adb0f07a..9c5a4deb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.9", "f98266488f9f152130a6015c7158f70ba8262939c4dd0728bb55ab48b6d282eb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "adb0853558c13cd77a4489e755df149e0e43bde9c069811ec244dd5dcbe62cd0"}, - "ash_sql": {:hex, :ash_sql, "0.2.1", "a1b10af77e1eb4e038cce7cbbd7cdf38c9982fba07587838f30fab7761596786", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1c0ecc87ce6ebcaa5b712f8d832e2e1845ab3330f86a377636e92d9e85f064f0"}, + "ash_sql": {:hex, :ash_sql, "0.2.2", "0ba1d3500d48db0d65f0dece06a5353865df4f79f7592aab50066ee2fcfc9524", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8ba70fb2257819c64518208948789a8ad1c15bfa1eb1f3850800052ef811b51c"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From a68229af075b627dcc48de4e0f182874efce70ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 08:20:58 -0400 Subject: [PATCH 0474/1215] chore(deps-dev): bump benchee from 1.3.0 to 1.3.1 (#316) Bumps [benchee](https://github.com/bencheeorg/benchee) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/bencheeorg/benchee/releases) - [Changelog](https://github.com/bencheeorg/benchee/blob/main/CHANGELOG.md) - [Commits](https://github.com/bencheeorg/benchee/compare/1.3.0...1.3.1) --- updated-dependencies: - dependency-name: benchee dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 9c5a4deb..84622395 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "ash": {:hex, :ash, "3.0.9", "f98266488f9f152130a6015c7158f70ba8262939c4dd0728bb55ab48b6d282eb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "adb0853558c13cd77a4489e755df149e0e43bde9c069811ec244dd5dcbe62cd0"}, "ash_sql": {:hex, :ash_sql, "0.2.2", "0ba1d3500d48db0d65f0dece06a5353865df4f79f7592aab50066ee2fcfc9524", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8ba70fb2257819c64518208948789a8ad1c15bfa1eb1f3850800052ef811b51c"}, - "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, + "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, From 9940edf633377d7f78c4bb075ce103a9d8f8914f Mon Sep 17 00:00:00 2001 From: Jesse Williams Date: Wed, 5 Jun 2024 10:12:32 -0700 Subject: [PATCH 0475/1215] test: demonstrating issue filtering relationship by parent (#318) --- ...hild_relationship_by_parent_relationsip.ex | 40 +++++++++++++++++++ test/support/resources/comment.ex | 7 ++++ 2 files changed, 47 insertions(+) create mode 100644 test/support/relationships/filter_child_relationship_by_parent_relationsip.ex diff --git a/test/support/relationships/filter_child_relationship_by_parent_relationsip.ex b/test/support/relationships/filter_child_relationship_by_parent_relationsip.ex new file mode 100644 index 00000000..983aa009 --- /dev/null +++ b/test/support/relationships/filter_child_relationship_by_parent_relationsip.ex @@ -0,0 +1,40 @@ +defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationship do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.{Comment, Post} + + require Ash.Query + + describe "loading ratings of a comment filtered by a post" do + setup do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "Post Title", score: 2}) + |> Ash.create!() + + ratings = + for i <- [1, 2, 2, 2, 3, 4, 5] do + %{score: i} + end + + comment = + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment Title"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.Changeset.manage_relationship(:ratings, ratings, type: :create) + |> Ash.create!() + + [post: post, comment: comment] + end + + test "it can load the ratings_with_same_score_as_post relationship", %{ + comment: comment + } do + comment = Ash.load!(comment, :ratings_with_same_score_as_post) + + ratings = comment.ratings_with_same_score_as_post + + assert Enum.count(ratings) == 3 + assert Enum.all?(ratings, &(&1.score == 2)) + end + end +end diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index c8888a8d..4253fbc9 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -71,5 +71,12 @@ defmodule AshPostgres.Test.Comment do relationship_context: %{data_layer: %{table: "comment_ratings"}}, filter: expr(score > 5) ) + + has_many(:ratings_with_same_score_as_post, AshPostgres.Test.Rating, + public?: true, + destination_attribute: :resource_id, + relationship_context: %{data_layer: %{table: "comment_ratings"}}, + filter: expr(parent(post.score) == score) + ) end end From 72ec53599f826af62d2f17f0abc05f785aecab7d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Jun 2024 13:10:05 -0400 Subject: [PATCH 0476/1215] fix: update ash_sql and fix issues retaining lateral join context --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 67ccb432..7bafd39d 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0 and >= 3.0.7")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.1")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.3")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 84622395..dcb0f666 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.9", "f98266488f9f152130a6015c7158f70ba8262939c4dd0728bb55ab48b6d282eb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "adb0853558c13cd77a4489e755df149e0e43bde9c069811ec244dd5dcbe62cd0"}, - "ash_sql": {:hex, :ash_sql, "0.2.2", "0ba1d3500d48db0d65f0dece06a5353865df4f79f7592aab50066ee2fcfc9524", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8ba70fb2257819c64518208948789a8ad1c15bfa1eb1f3850800052ef811b51c"}, + "ash_sql": {:hex, :ash_sql, "0.2.3", "ff2b5e61438f133cc895ed81c1fb76eac05059cd709cb307b9c9a4c9f11ffb5f", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "becbd5e527e33ed49bae7c78513e0136c5a050c143da299b8c0c735e22410c4a"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From c8a2729864769835305570d7a040113788395243 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Jun 2024 13:10:33 -0400 Subject: [PATCH 0477/1215] chore: release version v2.0.7 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa2fe57..5498a189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.7](https://github.com/ash-project/ash_postgres/compare/v2.0.6...v2.0.7) (2024-06-06) + + + + +### Bug Fixes: + +* update ash_sql and fix issues retaining lateral join context + +* ensure that all current attribute values are selected on bulk update shifted root query + ## [v2.0.6](https://github.com/ash-project/ash_postgres/compare/v2.0.5...v2.0.6) (2024-05-29) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 7bafd39d..d2e08217 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.6" + @version "2.0.7" def project do [ From 46452ba5331fea357ddbac5f027bcc347f574611 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Jun 2024 13:10:59 -0400 Subject: [PATCH 0478/1215] chore: update changelog --- CHANGELOG.md | 7 +--- ...lationship_by_parent_relationship_test.exs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 test/filter_child_relationship_by_parent_relationship_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5498a189..4c1c91a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,11 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.0.7](https://github.com/ash-project/ash_postgres/compare/v2.0.6...v2.0.7) (2024-06-06) - - - ### Bug Fixes: -* update ash_sql and fix issues retaining lateral join context +- [fix] update ash_sql and fix issues retaining lateral join context -* ensure that all current attribute values are selected on bulk update shifted root query +- [fix] ensure that all current attribute values are selected on bulk update shifted root query ## [v2.0.6](https://github.com/ash-project/ash_postgres/compare/v2.0.5...v2.0.6) (2024-05-29) diff --git a/test/filter_child_relationship_by_parent_relationship_test.exs b/test/filter_child_relationship_by_parent_relationship_test.exs new file mode 100644 index 00000000..983aa009 --- /dev/null +++ b/test/filter_child_relationship_by_parent_relationship_test.exs @@ -0,0 +1,40 @@ +defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationship do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.{Comment, Post} + + require Ash.Query + + describe "loading ratings of a comment filtered by a post" do + setup do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "Post Title", score: 2}) + |> Ash.create!() + + ratings = + for i <- [1, 2, 2, 2, 3, 4, 5] do + %{score: i} + end + + comment = + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment Title"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.Changeset.manage_relationship(:ratings, ratings, type: :create) + |> Ash.create!() + + [post: post, comment: comment] + end + + test "it can load the ratings_with_same_score_as_post relationship", %{ + comment: comment + } do + comment = Ash.load!(comment, :ratings_with_same_score_as_post) + + ratings = comment.ratings_with_same_score_as_post + + assert Enum.count(ratings) == 3 + assert Enum.all?(ratings, &(&1.score == 2)) + end + end +end From 4b5d980c69c88cbe2f4a8cfc871843db4e4e9c28 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Jun 2024 14:34:01 -0400 Subject: [PATCH 0479/1215] chore: undo accidental git stash for previous commit --- lib/data_layer.ex | 80 +++++++++---------- ...lationship_by_parent_relationship_test.exs | 2 +- test/sort_test.exs | 2 +- ...hild_relationship_by_parent_relationsip.ex | 40 ---------- 4 files changed, 39 insertions(+), 85 deletions(-) delete mode 100644 test/support/relationships/filter_child_relationship_by_parent_relationsip.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index cdf5bb71..1f87c194 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1105,18 +1105,7 @@ defmodule AshPostgres.DataLayer do source_pkey = Ash.Resource.Info.primary_key(source_query.resource) - source_query.resource - |> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]}) - |> Ash.Query.set_tenant(source_query.tenant) - |> set_lateral_join_prefix(query) - |> case do - %{valid?: true} = query -> - Ash.Query.data_layer_query(query) - - query -> - {:error, query} - end - |> case do + case lateral_join_source_query(query, source_query) do {:ok, data_layer_query} -> source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) @@ -1164,44 +1153,27 @@ defmodule AshPostgres.DataLayer do source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) source_pkey = Ash.Resource.Info.primary_key(source_query.resource) - through_resource - |> Ash.Query.new() - |> Ash.Query.set_context(through_relationship.context) - |> Ash.Query.do_filter(through_relationship.filter) - |> Ash.Query.set_tenant(source_query.tenant) - |> Ash.Query.put_context(:data_layer, %{ - start_bindings_at: query.__ash_bindings__.current - }) - |> set_lateral_join_prefix(query) - |> case do - %{valid?: true} = through_query -> - through_query - |> Ash.Query.data_layer_query() - - query -> - {:error, query} - end - |> case do - {:ok, through_query} -> - source_query.resource + case lateral_join_source_query(query, source_query) do + {:ok, data_layer_query} -> + through_resource |> Ash.Query.new() - |> Ash.Query.set_context(relationship.context) - |> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]}) |> Ash.Query.put_context(:data_layer, %{ - start_bindings_at: through_query.__ash_bindings__.current + start_bindings_at: data_layer_query.__ash_bindings__.current }) + |> Ash.Query.set_context(through_relationship.context) + |> Ash.Query.do_filter(through_relationship.filter) + |> Ash.Query.set_tenant(source_query.tenant) |> set_lateral_join_prefix(query) - |> Ash.Query.do_filter(relationship.filter) |> case do - %{valid?: true} = query -> - query + %{valid?: true} = through_query -> + through_query |> Ash.Query.data_layer_query() query -> {:error, query} end |> case do - {:ok, data_layer_query} -> + {:ok, through_query} -> if query.__ash_bindings__[:__order__?] do subquery = subquery( @@ -1214,14 +1186,14 @@ defmodule AshPostgres.DataLayer do source_query, relationship.through ), - as: ^query.__ash_bindings__.current, + as: ^data_layer_query.__ash_bindings__.current, on: field(through, ^destination_attribute_on_join_resource) == field(destination, ^destination_attribute), where: field(through, ^source_attribute_on_join_resource) == field( - parent_as(^through_query.__ash_bindings__.current), + parent_as(^0), ^source_attribute ) ) @@ -1252,14 +1224,14 @@ defmodule AshPostgres.DataLayer do source_query, relationship.through ), - as: ^query.__ash_bindings__.current, + as: ^data_layer_query.__ash_bindings__.current, on: field(through, ^destination_attribute_on_join_resource) == field(destination, ^destination_attribute), where: field(through, ^source_attribute_on_join_resource) == field( - parent_as(^through_query.__ash_bindings__.current), + parent_as(^0), ^source_attribute ) ) @@ -1289,6 +1261,28 @@ defmodule AshPostgres.DataLayer do end end + defp lateral_join_source_query( + %{__ash_bindings__: %{lateral_join_source_query: lateral_join_source_query}}, + _ + ) + when not is_nil(lateral_join_source_query) do + {:ok, lateral_join_source_query} + end + + defp lateral_join_source_query(query, source_query) do + source_query.resource + |> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]}) + |> Ash.Query.set_tenant(source_query.tenant) + |> set_lateral_join_prefix(query) + |> case do + %{valid?: true} = query -> + Ash.Query.data_layer_query(query) + + query -> + {:error, query} + end + end + @doc false def set_subquery_prefix(data_layer_query, source_query, resource) do repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) diff --git a/test/filter_child_relationship_by_parent_relationship_test.exs b/test/filter_child_relationship_by_parent_relationship_test.exs index 983aa009..fb2f4e52 100644 --- a/test/filter_child_relationship_by_parent_relationship_test.exs +++ b/test/filter_child_relationship_by_parent_relationship_test.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationship do +defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationshipTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Comment, Post} diff --git a/test/sort_test.exs b/test/sort_test.exs index 1d190e8b..a99b3233 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -218,7 +218,7 @@ defmodule AshPostgres.SortTest do |> Ash.create!() posts_query = - Ash.Query.sort(Post, Ash.Sort.expr_sort(source(post_links.state))) + Ash.Query.sort(Post, Ash.Sort.expr_sort(parent(post_links.state))) Post |> Ash.Query.load(linked_posts: posts_query) diff --git a/test/support/relationships/filter_child_relationship_by_parent_relationsip.ex b/test/support/relationships/filter_child_relationship_by_parent_relationsip.ex deleted file mode 100644 index 983aa009..00000000 --- a/test/support/relationships/filter_child_relationship_by_parent_relationsip.ex +++ /dev/null @@ -1,40 +0,0 @@ -defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationship do - use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Comment, Post} - - require Ash.Query - - describe "loading ratings of a comment filtered by a post" do - setup do - post = - Post - |> Ash.Changeset.for_create(:create, %{title: "Post Title", score: 2}) - |> Ash.create!() - - ratings = - for i <- [1, 2, 2, 2, 3, 4, 5] do - %{score: i} - end - - comment = - Comment - |> Ash.Changeset.for_create(:create, %{title: "Comment Title"}) - |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) - |> Ash.Changeset.manage_relationship(:ratings, ratings, type: :create) - |> Ash.create!() - - [post: post, comment: comment] - end - - test "it can load the ratings_with_same_score_as_post relationship", %{ - comment: comment - } do - comment = Ash.load!(comment, :ratings_with_same_score_as_post) - - ratings = comment.ratings_with_same_score_as_post - - assert Enum.count(ratings) == 3 - assert Enum.all?(ratings, &(&1.score == 2)) - end - end -end From a9aca443a0ba7c889e3a70df8554a0f7abccfb00 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Jun 2024 14:35:46 -0400 Subject: [PATCH 0480/1215] chore: release version v2.0.8 --- CHANGELOG.md | 5 +++++ mix.exs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1c91a0..09b5646d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.8](https://github.com/ash-project/ash_postgres/compare/v2.0.7...v2.0.8) (2024-06-06) + + + + ## [v2.0.7](https://github.com/ash-project/ash_postgres/compare/v2.0.6...v2.0.7) (2024-06-06) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index d2e08217..e9c33c1f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.7" + @version "2.0.8" def project do [ From 57b433023eeaa032a16db293e28a221a4df08bfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:07:42 -0400 Subject: [PATCH 0481/1215] chore(deps): bump ash from 3.0.9 to 3.0.10 (#319) Bumps [ash](https://github.com/ash-project/ash) from 3.0.9 to 3.0.10. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.0.9...v3.0.10) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index dcb0f666..08fe4b9f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.9", "f98266488f9f152130a6015c7158f70ba8262939c4dd0728bb55ab48b6d282eb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "adb0853558c13cd77a4489e755df149e0e43bde9c069811ec244dd5dcbe62cd0"}, + "ash": {:hex, :ash, "3.0.10", "cec1140498da12c5dc4b27dfc8885fb1a4ffc9e5f67564668af8ca87b6ee5ced", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4493b9c853a523322791f06a3e353c2943cb22ce68cfb87ebac694b4d73dd0cc"}, "ash_sql": {:hex, :ash_sql, "0.2.3", "ff2b5e61438f133cc895ed81c1fb76eac05059cd709cb307b9c9a4c9f11ffb5f", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "becbd5e527e33ed49bae7c78513e0136c5a050c143da299b8c0c735e22410c4a"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 6c81699b0da7f3d6103605d35df3917ea64eed0a Mon Sep 17 00:00:00 2001 From: Robert Timis <65460527+TimisRobert@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:43:24 +0200 Subject: [PATCH 0482/1215] feat: autogenerate index in references (#321) * feat: autogenerate index in references * check if old had index --- .../migration_generator.ex | 60 +++++++++ lib/migration_generator/operation.ex | 65 ++++++++++ lib/reference.ex | 8 +- test/migration_generator_test.exs | 118 ++++++++++++++++++ 4 files changed, 250 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 76ab35c2..f04039ec 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -625,6 +625,7 @@ defmodule AshPostgres.MigrationGenerator do %{ destination_attribute: merge_uniq!(references, table, :destination_attribute, name), deferrable: merge_uniq!(references, table, :deferrable, name), + index?: merge_uniq!(references, table, :index?, name), destination_attribute_default: merge_uniq!(references, table, :destination_attribute_default, name), destination_attribute_generated: @@ -1237,6 +1238,16 @@ defmodule AshPostgres.MigrationGenerator do true end + defp after?( + %Operation.AddReferenceIndex{ + table: table, + schema: schema + }, + %{table: table, schema: schema} + ) do + true + end + defp after?( %Operation.AddCheckConstraint{ constraint: %{attribute: attribute_or_attributes}, @@ -1260,6 +1271,16 @@ defmodule AshPostgres.MigrationGenerator do true end + defp after?( + %Operation.AddReferenceIndex{ + table: table, + schema: schema + }, + %Operation.AddAttribute{table: table, schema: schema} + ) do + true + end + defp after?( %Operation.AddCustomIndex{ table: table, @@ -1736,6 +1757,40 @@ defmodule AshPostgres.MigrationGenerator do } end) + reference_indexes_to_add = + Enum.filter(snapshot.attributes, fn attribute -> + if attribute.references, do: attribute.references.index? + end) + |> Enum.map(fn attribute -> + %Operation.AddReferenceIndex{ + table: snapshot.table, + schema: snapshot.schema, + source: attribute.source, + multitenancy: snapshot.multitenancy + } + end) + + reference_indexes_to_remove = + Enum.filter(old_snapshot.attributes, fn old_attribute -> + attribute = + Enum.find(snapshot.attributes, fn attribute -> + attribute.source == old_attribute.source + end) + + has_removed_index? = attribute && not attribute.index? && old_attribute.index? + attribute_doesnt_exist? = !attribute && old_attribute.index? + + has_removed_index? or attribute_doesnt_exist? + end) + |> Enum.map(fn attribute -> + %Operation.RemoveReferenceIndex{ + table: snapshot.table, + schema: snapshot.schema, + source: attribute.source, + multitenancy: snapshot.multitenancy + } + end) + custom_indexes_to_remove = Enum.filter(old_snapshot.custom_indexes, fn old_custom_index -> (rewrite_all_identities? && !old_custom_index.all_tenants?) || @@ -1881,6 +1936,8 @@ defmodule AshPostgres.MigrationGenerator do pkey_operations, unique_indexes_to_remove, attribute_operations, + reference_indexes_to_add, + reference_indexes_to_remove, unique_indexes_to_add, unique_indexes_to_rename, constraints_to_remove, @@ -2500,6 +2557,7 @@ defmodule AshPostgres.MigrationGenerator do AshPostgres.DataLayer.Info.repo(relationship.destination, :mutate) ), deferrable: false, + index?: false, destination_attribute_generated: source_attribute.generated?, multitenancy: multitenancy(relationship.source), table: AshPostgres.DataLayer.Info.table(relationship.source), @@ -2710,6 +2768,7 @@ defmodule AshPostgres.MigrationGenerator do %{ destination_attribute: destination_attribute_source, deferrable: configured_reference.deferrable, + index?: configured_reference.index?, multitenancy: multitenancy(relationship.destination), on_delete: configured_reference.on_delete, on_update: configured_reference.on_update, @@ -2743,6 +2802,7 @@ defmodule AshPostgres.MigrationGenerator do match_with: nil, match_type: nil, deferrable: false, + index?: false, schema: relationship.context[:data_layer][:schema] || AshPostgres.DataLayer.Info.schema(relationship.destination) || diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index e57629e7..4198baa3 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -967,6 +967,57 @@ defmodule AshPostgres.MigrationGenerator.Operation do end end + defmodule AddReferenceIndex do + @moduledoc false + defstruct [:table, :schema, :source, :multitenancy, no_phase: true] + import Helper + + def up(%{ + source: source, + table: table, + schema: schema, + multitenancy: multitenancy + }) do + keys = + if multitenancy.strategy == :attribute do + [multitenancy.attribute, source] + else + [source] + end + + opts = + join([ + option(:prefix, schema) + ]) + + if opts == "" do + "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])" + else + "create index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})" + end + end + + def down(%{schema: schema, source: source, table: table, multitenancy: multitenancy}) do + keys = + if multitenancy.strategy == :attribute do + [multitenancy.attribute, source] + else + [source] + end + + opts = + join([ + option(:prefix, schema) + ]) + + if opts == "" do + "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}])" + else + "drop_if_exists index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{opts})" + end + end + end + defmodule RemovePrimaryKey do @moduledoc false defstruct [:schema, :table, no_phase: true] @@ -1025,6 +1076,20 @@ defmodule AshPostgres.MigrationGenerator.Operation do end end + defmodule RemoveReferenceIndex do + @moduledoc false + defstruct [:schema, :table, :source, :multitenancy, no_phase: true] + import Helper + + def up(operation) do + AddReferenceIndex.down(operation) + end + + def down(operation) do + AddReferenceIndex.up(operation) + end + end + defmodule RenameUniqueIndex do @moduledoc false defstruct [ diff --git a/lib/reference.ex b/lib/reference.ex index ab980036..724251e0 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -8,6 +8,7 @@ defmodule AshPostgres.Reference do :match_with, :match_type, :deferrable, + :index?, ignore?: false ] @@ -44,7 +45,7 @@ defmodule AshPostgres.Reference do type: {:one_of, [false, true, :initially]}, default: false, doc: """ - Wether or not the constraint is deferrable. This only affects the migration generator. + Whether or not the constraint is deferrable. This only affects the migration generator. """ ], name: [ @@ -60,6 +61,11 @@ defmodule AshPostgres.Reference do match_type: [ type: {:one_of, [:simple, :partial, :full]}, doc: "select if the match is `:simple`, `:partial`, or `:full`" + ], + index?: [ + type: :boolean, + default: false, + doc: "Whether to create or not a corresponding index" ] ] end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index bcbb59a5..2b97ebcd 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -996,6 +996,124 @@ defmodule AshPostgres.MigrationGeneratorTest do ~S{references(:posts, column: :id, with: [related_key_id: :key_id], match: :partial, name: "posts_post_id_fkey", type: :uuid, prefix: "public")} end + test "references generate related index when index? true" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:key_id, :uuid, allow_nil?: false, public?: true) + attribute(:foobar, :string, public?: true) + end + end + + defposts Post2 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, Post) do + public?(true) + end + end + + postgres do + references do + reference(:post, index?: true) + end + end + end + + defdomain([Post, Post2]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ ~S{create index(:posts, [:post_id])} + end + + test "index generated by index? true also adds column when using attribute multitenancy" do + defresource Org, "orgs" do + attributes do + uuid_primary_key(:id, writable?: true, public?: true) + attribute(:name, :string, public?: true) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + end + + defposts do + attributes do + uuid_primary_key(:id) + attribute(:key_id, :uuid, allow_nil?: false, public?: true) + attribute(:foobar, :string, public?: true) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) do + public?(true) + end + end + end + + defposts Post2 do + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + attribute(:related_key_id, :uuid, public?: true) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:post, Post) do + public?(true) + end + + belongs_to(:org, Org) do + public?(true) + end + end + + postgres do + references do + reference(:post, index?: true) + end + end + end + + defdomain([Org, Post, Post2]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + + assert File.read!(file) =~ ~S{create index(:posts, [:org_id, :post_id])} + end + test "references merge :match_with and multitenancy attribute" do defresource Org, "orgs" do attributes do From f0779580b3ce1e0817477afe3638e9bd2cbaa844 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 10 Jun 2024 09:08:11 -0400 Subject: [PATCH 0483/1215] fix: `list_tenants` -> `all_tenants` fix: when checking for roll back-able migrations, only check `Path.basename` --- lib/data_layer.ex | 72 ++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1f87c194..3d2e2362 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -447,7 +447,7 @@ defmodule AshPostgres.DataLayer do |> Enum.sort() |> Enum.reverse() |> Enum.filter(fn file -> - Enum.any?(current_migrations, &String.starts_with?(file, &1)) + Enum.any?(current_migrations, &String.starts_with?(Path.basename(file), &1)) end) |> Enum.take(20) |> Enum.map(&String.trim_leading(&1, migrations_path)) @@ -484,31 +484,37 @@ defmodule AshPostgres.DataLayer do Mix.Task.run("ash_postgres.rollback", args ++ ["-r", inspect(repo), "-n", to_string(n)]) Mix.Task.reenable("ash_postgres.rollback") - first_tenant = repo.list_tenants() |> Enum.at(0) + tenant_files = + tenant_migrations_path + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.sort() + |> Enum.reverse() + + if !Enum.empty?(tenant_files) do + first_tenant = repo.all_tenants() |> Enum.at(0) + + if first_tenant do + current_tenant_migrations = + Ecto.Query.from(row in "schema_migrations", + select: row.version + ) + |> repo.all(prefix: first_tenant) + |> Enum.map(&to_string/1) + + tenant_files = + tenant_files + |> Enum.filter(fn file -> + Enum.any?( + current_tenant_migrations, + &String.starts_with?(Path.basename(file), &1) + ) + end) + |> Enum.take(20) + |> Enum.map(&String.trim_leading(&1, tenant_migrations_path)) + |> Enum.with_index() + |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - if first_tenant do - current_tenant_migrations = - Ecto.Query.from(row in "schema_migrations", - select: row.version - ) - |> repo.all(prefix: first_tenant) - |> Enum.map(&to_string/1) - - tenant_files = - tenant_migrations_path - |> Path.join("**/*.exs") - |> Path.wildcard() - |> Enum.sort() - |> Enum.reverse() - |> Enum.filter(fn file -> - Enum.any?(current_tenant_migrations, &String.starts_with?(file, &1)) - end) - |> Enum.take(20) - |> Enum.map(&String.trim_leading(&1, tenant_migrations_path)) - |> Enum.with_index() - |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - - if !Enum.empty?(tenant_files) do n = Mix.shell().prompt( """ @@ -565,13 +571,7 @@ defmodule AshPostgres.DataLayer do [] |> AshPostgres.Mix.Helpers.repos!(args) - |> Enum.all?(fn repo -> - [] - |> AshPostgres.Mix.Helpers.tenant_migrations_path(repo) - |> Path.join("**/*.exs") - |> Path.wildcard() - |> Enum.empty?() - end) + |> Enum.all?(&(not has_tenant_migrations?(&1))) |> case do true -> :ok @@ -586,6 +586,14 @@ defmodule AshPostgres.DataLayer do Mix.Task.run("ash_postgres.drop", args) end + defp has_tenant_migrations?(repo) do + [] + |> AshPostgres.Mix.Helpers.tenant_migrations_path(repo) + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.empty?() + end + import Ecto.Query, only: [from: 2, subquery: 1] @impl true From 2c31b3e167e3b5ce131a748896524df2f3ce0482 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 10 Jun 2024 11:10:19 -0400 Subject: [PATCH 0484/1215] fix: don't assume old snapshots have `index?` key for attributes --- lib/migration_generator/migration_generator.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index f04039ec..85e6dc55 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1759,7 +1759,7 @@ defmodule AshPostgres.MigrationGenerator do reference_indexes_to_add = Enum.filter(snapshot.attributes, fn attribute -> - if attribute.references, do: attribute.references.index? + if attribute.references, do: attribute.references[:index?] end) |> Enum.map(fn attribute -> %Operation.AddReferenceIndex{ @@ -1777,10 +1777,10 @@ defmodule AshPostgres.MigrationGenerator do attribute.source == old_attribute.source end) - has_removed_index? = attribute && not attribute.index? && old_attribute.index? - attribute_doesnt_exist? = !attribute && old_attribute.index? + has_removed_index? = attribute && !attribute[:index?] && old_attribute[:index?] + attribute_doesnt_exist? = !attribute && old_attribute[:index?] - has_removed_index? or attribute_doesnt_exist? + has_removed_index? || attribute_doesnt_exist? end) |> Enum.map(fn attribute -> %Operation.RemoveReferenceIndex{ From 8e32e0ab9a58d72161c35fdeaefa285ef3803329 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 10 Jun 2024 12:49:04 -0400 Subject: [PATCH 0485/1215] fix: ensure that context multitenancy is properly applied to lateral many-to-many joins --- .formatter.exs | 1 + .../dsls/DSL:-AshPostgres.DataLayer.md | 3 +- lib/data_layer.ex | 12 ++- .../migration_generator.ex | 7 +- .../tenants/friend_links/20240610162043.json | 77 +++++++++++++++++++ .../20240610162043_migrate_resources3.exs | 45 +++++++++++ test/multitenancy_test.exs | 65 ++++++++++++++-- test/support/multitenancy/domain.ex | 1 + test/support/multitenancy/resources/post.ex | 6 ++ .../multitenancy/resources/post_link.ex | 31 ++++++++ 10 files changed, 235 insertions(+), 13 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json create mode 100644 priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs create mode 100644 test/support/multitenancy/resources/post_link.ex diff --git a/.formatter.exs b/.formatter.exs index 786ee5de..8211617e 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -19,6 +19,7 @@ spark_locals_without_parens = [ include: 1, index: 1, index: 2, + index?: 1, match_type: 1, match_with: 1, message: 1, diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 7d6cde44..2579f609 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -301,10 +301,11 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post | [`ignore?`](#postgres-references-reference-ignore?){: #postgres-references-reference-ignore? } | `boolean` | | If set to true, no reference is created for the given relationship. This is useful if you need to define it in some custom way | | [`on_delete`](#postgres-references-reference-on_delete){: #postgres-references-reference-on_delete } | `:delete \| :nilify \| :nothing \| :restrict \| {:nilify, atom \| list(atom)}` | | What should happen to records of this resource when the referenced record of the *destination* resource is deleted. | | [`on_update`](#postgres-references-reference-on_update){: #postgres-references-reference-on_update } | `:update \| :nilify \| :nothing \| :restrict` | | What should happen to records of this resource when the referenced destination_attribute of the *destination* record is update. | -| [`deferrable`](#postgres-references-reference-deferrable){: #postgres-references-reference-deferrable } | `false \| true \| :initially` | `false` | Wether or not the constraint is deferrable. This only affects the migration generator. | +| [`deferrable`](#postgres-references-reference-deferrable){: #postgres-references-reference-deferrable } | `false \| true \| :initially` | `false` | Whether or not the constraint is deferrable. This only affects the migration generator. | | [`name`](#postgres-references-reference-name){: #postgres-references-reference-name } | `String.t` | | The name of the foreign key to generate in the database. Defaults to
__fkey | | [`match_with`](#postgres-references-reference-match_with){: #postgres-references-reference-match_with } | `keyword` | | Defines additional keys to the foreign key in order to build a composite foreign key. The key should be the name of the source attribute (in the current resource), the value the name of the destination attribute. | | [`match_type`](#postgres-references-reference-match_type){: #postgres-references-reference-match_type } | `:simple \| :partial \| :full` | | select if the match is `:simple`, `:partial`, or `:full` | +| [`index?`](#postgres-references-reference-index?){: #postgres-references-reference-index? } | `boolean` | `false` | Whether to create or not a corresponding index | diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3d2e2362..22589331 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1270,11 +1270,17 @@ defmodule AshPostgres.DataLayer do end defp lateral_join_source_query( - %{__ash_bindings__: %{lateral_join_source_query: lateral_join_source_query}}, - _ + %{ + __ash_bindings__: %{ + lateral_join_source_query: lateral_join_source_query + } + }, + source_query ) when not is_nil(lateral_join_source_query) do - {:ok, lateral_join_source_query} + {:ok, + lateral_join_source_query + |> set_subquery_prefix(source_query, lateral_join_source_query.__ash_bindings__.resource)} end defp lateral_join_source_query(query, source_query) do diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 85e6dc55..be748f82 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1777,8 +1777,10 @@ defmodule AshPostgres.MigrationGenerator do attribute.source == old_attribute.source end) - has_removed_index? = attribute && !attribute[:index?] && old_attribute[:index?] - attribute_doesnt_exist? = !attribute && old_attribute[:index?] + has_removed_index? = + attribute && !attribute[:references][:index?] && old_attribute[:references][:index?] + + attribute_doesnt_exist? = !attribute && old_attribute[:references][:index?] has_removed_index? || attribute_doesnt_exist? end) @@ -3173,6 +3175,7 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put_new(:on_update, nil) |> Map.update!(:on_delete, &(&1 && load_references_on_delete(&1))) |> Map.update!(:on_update, &(&1 && maybe_to_atom(&1))) + |> Map.put_new(:index?, false) |> Map.put_new(:match_with, nil) |> Map.put_new(:match_type, nil) |> Map.update!( diff --git a/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json b/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json new file mode 100644 index 00000000..5fe7e14d --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json @@ -0,0 +1,77 @@ +{ + "attributes": [ + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "source_id", + "references": { + "name": "friend_links_source_id_fkey", + "table": "multitenant_posts", + "multitenancy": { + "global": false, + "attribute": null, + "strategy": "context" + }, + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "dest_id", + "references": { + "name": "friend_links_dest_id_fkey", + "table": "multitenant_posts", + "multitenancy": { + "global": false, + "attribute": null, + "strategy": "context" + }, + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + } + ], + "table": "friend_links", + "hash": "880BED202EB36FA2543D5DCC25DE1373676CE38CECFA2C8B5651757FFF3817EF", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": false, + "attribute": null, + "strategy": "context" + }, + "schema": null, + "check_constraints": [], + "identities": [], + "custom_indexes": [], + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs b/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs new file mode 100644 index 00000000..dc378342 --- /dev/null +++ b/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs @@ -0,0 +1,45 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources3 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 + create table(:friend_links, primary_key: false, prefix: prefix()) do + add( + :source_id, + references(:multitenant_posts, + column: :id, + name: "friend_links_source_id_fkey", + type: :uuid, + prefix: prefix() + ), + primary_key: true, + null: false + ) + + add( + :dest_id, + references(:multitenant_posts, + column: :id, + name: "friend_links_dest_id_fkey", + type: :uuid, + prefix: prefix() + ), + primary_key: true, + null: false + ) + end + end + + def down do + drop(constraint(:friend_links, "friend_links_source_id_fkey")) + + drop(constraint(:friend_links, "friend_links_dest_id_fkey")) + + drop(table(:friend_links, prefix: prefix())) + end +end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index d38c37a6..cb69da08 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -109,23 +109,74 @@ defmodule AshPostgres.Test.MultitenancyTest do |> Ash.Changeset.new() |> Ash.create!() - user1 = + user = User - |> Ash.Changeset.for_create(:create, %{name: "a"}) + |> Ash.Changeset.for_create(:create, %{name: "a"}, tenant: "org_#{org.id}") |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.create!() user2 = User - |> Ash.Changeset.for_create(:create, %{name: "b"}) + |> Ash.Changeset.for_create(:create, %{name: "a"}, tenant: "org_#{org.id}") |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.create!() - user1_id = user1.id - user2_id = user2.id + post = + Post + |> Ash.Changeset.for_create(:create, %{name: "foobar"}, + authorize?: false, + tenant: "org_#{org.id}" + ) + |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) + |> Ash.create!() - assert [%{id: ^user1_id}, %{id: ^user2_id}] = - Ash.load!(org, users: Ash.Query.sort(User, :name)).users + post_id = post.id + + assert [%{posts: [%{id: ^post_id}]}, _] = + Ash.load!([user, user2], [posts: Ash.Query.limit(Post, 2)], + tenant: "org_#{org.id}", + authorize?: false + ) + end + + test "loading context multitenant resources across a many-to-many with a limit works" do + org = + Org + |> Ash.Changeset.new() + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{name: "a"}, tenant: "org_#{org.id}") + |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{name: "foobar"}, + authorize?: false, + tenant: "org_#{org.id}" + ) + |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{name: "foobar"}, + authorize?: false, + tenant: "org_#{org.id}" + ) + |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) + |> Ash.Changeset.manage_relationship(:linked_posts, post, type: :append_and_remove) + |> Ash.create!() + + post_id = post.id + + assert [%{linked_posts: [%{id: ^post_id}]}, _] = + Ash.load!([post2, post], [linked_posts: Ash.Query.limit(Post, 2)], + tenant: "org_#{org.id}", + authorize?: false + ) end test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 6384d0da..664b3919 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -6,5 +6,6 @@ defmodule AshPostgres.MultitenancyTest.Domain do resource(AshPostgres.MultitenancyTest.Org) resource(AshPostgres.MultitenancyTest.User) resource(AshPostgres.MultitenancyTest.Post) + resource(AshPostgres.MultitenancyTest.PostLink) end end diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index 35cc9adf..d7999771 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -50,6 +50,12 @@ defmodule AshPostgres.MultitenancyTest.Post do end has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id, public?: true) + + many_to_many :linked_posts, __MODULE__ do + through(AshPostgres.MultitenancyTest.PostLink) + source_attribute_on_join_resource(:source_id) + destination_attribute_on_join_resource(:dest_id) + end end calculations do diff --git a/test/support/multitenancy/resources/post_link.ex b/test/support/multitenancy/resources/post_link.ex new file mode 100644 index 00000000..bd22c558 --- /dev/null +++ b/test/support/multitenancy/resources/post_link.ex @@ -0,0 +1,31 @@ +defmodule AshPostgres.MultitenancyTest.PostLink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "friend_links" + repo AshPostgres.TestRepo + end + + multitenancy do + strategy(:context) + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + relationships do + belongs_to(:source, AshPostgres.MultitenancyTest.Post, + primary_key?: true, + allow_nil?: false + ) + + belongs_to(:dest, AshPostgres.MultitenancyTest.Post, + primary_key?: true, + allow_nil?: false + ) + end +end From 49db2be6b23982cf5905b32550fd69900d8897be Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 10 Jun 2024 15:57:04 -0400 Subject: [PATCH 0486/1215] fix: fix error message displaying in identity verifier closes #320 --- lib/verifiers/validate_identity_index_names.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index 235389af..7c046869 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -26,7 +26,7 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do |> Enum.map(fn identity -> {identity, identity_index_names[identity.name] || "#{table}_#{identity.name}_index"} end) - |> Enum.group_by(&elem(&1, 1)) + |> Enum.group_by(&elem(&1, 1), &elem(&1, 0)) |> Enum.each(fn {name, [_, _ | _] = identities} -> raise Spark.Error.DslError, From 85620a0f8958b46c91f04ff5bed61cdcf512fe28 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 10 Jun 2024 15:59:05 -0400 Subject: [PATCH 0487/1215] improvement: don't sort identity keys. There are important implications here, specifically that there are optimizations that can be had based on what the first column in a unique constraint is, and we want to make sure that can be taken advantage of. closes #313 --- .../migration_generator.ex | 13 +-- .../test_repo/post_links/20240610195853.json | 100 ++++++++++++++++++ .../20240610195853_migrate_resources27.exs | 37 +++++++ 3 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/post_links/20240610195853.json create mode 100644 priv/test_repo/migrations/20240610195853_migrate_resources27.exs diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index be748f82..7c51e9ea 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -557,7 +557,7 @@ defmodule AshPostgres.MigrationGenerator do |> Kernel.!() end) |> Enum.uniq_by(fn identity -> - {Enum.sort(identity.keys), identity.base_filter} + {identity.keys, identity.base_filter} end) new_snapshot = %{new_snapshot | identities: all_identities} @@ -740,7 +740,7 @@ defmodule AshPostgres.MigrationGenerator do identities = Enum.reject(identities, fn identity -> - Enum.sort(identity.keys) == primary_key + identity.keys == primary_key end) {primary_key, identities} @@ -1817,7 +1817,7 @@ defmodule AshPostgres.MigrationGenerator do Enum.reject(old_snapshot.identities, fn old_identity -> Enum.find(snapshot.identities, fn identity -> identity.name == old_identity.name && - Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && + old_identity.keys == identity.keys && old_identity.base_filter == identity.base_filter && old_identity.all_tenants? == identity.all_tenants? && old_identity.nils_distinct? == identity.nils_distinct? && @@ -1874,7 +1874,7 @@ defmodule AshPostgres.MigrationGenerator do if identity.all_tenants? do Enum.find(old_snapshot.identities, fn old_identity -> old_identity.name == identity.name && - Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && + old_identity.keys == identity.keys && old_identity.base_filter == identity.base_filter && old_identity.all_tenants? == identity.all_tenants? && old_identity.nils_distinct? == identity.nils_distinct? && @@ -1888,7 +1888,7 @@ defmodule AshPostgres.MigrationGenerator do Enum.reject(snapshot.identities, fn identity -> Enum.find(old_snapshot.identities, fn old_identity -> old_identity.name == identity.name && - Enum.sort(old_identity.keys) == Enum.sort(identity.keys) && + old_identity.keys == identity.keys && old_identity.base_filter == identity.base_filter && old_identity.all_tenants? == identity.all_tenants? && old_identity.nils_distinct? == identity.nils_distinct? && @@ -3244,9 +3244,6 @@ defmodule AshPostgres.MigrationGenerator do defp load_identity(identity, table) do identity |> Map.update!(:name, &maybe_to_atom/1) - |> Map.update!(:keys, fn keys -> - Enum.sort(keys) - end) |> add_index_name(table) |> Map.put_new(:base_filter, nil) |> Map.put_new(:all_tenants?, false) diff --git a/priv/resource_snapshots/test_repo/post_links/20240610195853.json b/priv/resource_snapshots/test_repo/post_links/20240610195853.json new file mode 100644 index 00000000..a0311d8a --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20240610195853.json @@ -0,0 +1,100 @@ +{ + "attributes": [ + { + "default": "\"active\"", + "size": null, + "type": "text", + "source": "state", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "source_post_id", + "references": { + "name": "post_links_source_post_id_fkey", + "table": "posts", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "destination_post_id", + "references": { + "name": "post_links_destination_post_id_fkey", + "table": "posts", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "schema": "public", + "deferrable": false, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "on_delete": null, + "on_update": null, + "match_with": null, + "match_type": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + } + ], + "table": "post_links", + "hash": "AF3FA145E25BB98CD83D51B551C8E623F1CE088DA1DE09B9905CA5E4B085C872", + "repo": "Elixir.AshPostgres.TestRepo", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "schema": null, + "identities": [ + { + "name": "unique_link", + "keys": [ + "destination_post_id", + "source_post_id" + ], + "where": null, + "base_filter": null, + "all_tenants?": false, + "nils_distinct?": true, + "index_name": "post_links_unique_link_index" + } + ], + "has_create_action": true, + "custom_indexes": [], + "custom_statements": [], + "base_filter": null, + "check_constraints": [] +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240610195853_migrate_resources27.exs b/priv/test_repo/migrations/20240610195853_migrate_resources27.exs new file mode 100644 index 00000000..c31f8819 --- /dev/null +++ b/priv/test_repo/migrations/20240610195853_migrate_resources27.exs @@ -0,0 +1,37 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources27 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 + drop_if_exists( + unique_index(:post_links, ["source_post_id", "destination_post_id"], + name: "post_links_unique_link_index" + ) + ) + + create( + unique_index(:post_links, ["destination_post_id", "source_post_id"], + name: "post_links_unique_link_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:post_links, ["destination_post_id", "source_post_id"], + name: "post_links_unique_link_index" + ) + ) + + create( + unique_index(:post_links, ["source_post_id", "destination_post_id"], + name: "post_links_unique_link_index" + ) + ) + end +end From b9e2fe45f4312d456d47e22df136f6715f078597 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 11 Jun 2024 12:08:10 -0400 Subject: [PATCH 0488/1215] test: add `@moduletag :migration` to squash snapshots test --- test/mix_squash_snapshots_test.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/mix_squash_snapshots_test.exs b/test/mix_squash_snapshots_test.exs index b9ac91e6..5c934b27 100644 --- a/test/mix_squash_snapshots_test.exs +++ b/test/mix_squash_snapshots_test.exs @@ -1,5 +1,6 @@ defmodule AshPostgres.MixSquashSnapshotsTest do use AshPostgres.RepoCase, async: false + @moduletag :migration defmacrop defposts(mod \\ Post, do: body) do quote do From 2bee2638a0db46a45d570743ac6895f200c6c859 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 09:30:23 -0400 Subject: [PATCH 0489/1215] chore(deps): bump ash from 3.0.10 to 3.0.11 (#323) Bumps [ash](https://github.com/ash-project/ash) from 3.0.10 to 3.0.11. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.0.10...v3.0.11) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 08fe4b9f..b9c4f6d6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.10", "cec1140498da12c5dc4b27dfc8885fb1a4ffc9e5f67564668af8ca87b6ee5ced", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4493b9c853a523322791f06a3e353c2943cb22ce68cfb87ebac694b4d73dd0cc"}, + "ash": {:hex, :ash, "3.0.11", "49b06de5c533ac46cb820430d5dad9ab9f566881e7a4a400e4ba11620657b1b3", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "89c91c6f4472a199eb4e3e2f835360c85b4fd9a12cc0891c3d6feb6ec4bd4e82"}, "ash_sql": {:hex, :ash_sql, "0.2.3", "ff2b5e61438f133cc895ed81c1fb76eac05059cd709cb307b9c9a4c9f11ffb5f", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "becbd5e527e33ed49bae7c78513e0136c5a050c143da299b8c0c735e22410c4a"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -31,7 +31,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"}, - "spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"}, + "spark": {:hex, :spark, "2.1.24", "fb596da83ee85b83f27f3a98df226869963ec5e423b4137884a6176f332e84ff", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e57f62fe1de1b327c2bfe04473496497edb6817371ba677099389deda38da90d"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.0", "ef3a7cac0f200c43caf3e6caf9be63115851b4f1cde3f21afaab220adc40e3d7", [:mix], [], "hexpm", "cccc411d5facf1bab86e7c671382d164f05f8992574c95349d3c8b317e14d953"}, From 9531ffa0c480259d0d3d1eeb7e01334a377a039d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 08:22:04 -0400 Subject: [PATCH 0490/1215] chore(deps-dev): bump credo from 1.7.6 to 1.7.7 (#325) Bumps [credo](https://github.com/rrrene/credo) from 1.7.6 to 1.7.7. - [Release notes](https://github.com/rrrene/credo/releases) - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/compare/v1.7.6...v1.7.7) --- updated-dependencies: - dependency-name: credo dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index b9c4f6d6..f7ec150d 100644 --- a/mix.lock +++ b/mix.lock @@ -4,7 +4,7 @@ "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, - "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, From 27837d1e308e8e4db6e8863c83fe8625d8d339e9 Mon Sep 17 00:00:00 2001 From: Jefferson Queiroz Venerando Date: Thu, 13 Jun 2024 08:28:27 -0400 Subject: [PATCH 0491/1215] test: Add a failing test for sorting on relationship (#324) * add a failing test for sorting on relationship * fix relationship pattern match * fix title sort --- test/sort_test.exs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/sort_test.exs b/test/sort_test.exs index a99b3233..88746b2d 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.SortTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Comment, Post, PostLink} + alias AshPostgres.Test.{Comment, Post, PostLink, PostView} require Ash.Query require Ash.Sort @@ -224,4 +224,46 @@ defmodule AshPostgres.SortTest do |> Ash.Query.load(linked_posts: posts_query) |> Ash.read!() end + + test "sorting on relationship attributes work" do + post1 = + Post + |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) + |> Ash.create!() + + view1 = + PostView + |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post1.id}) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0}) + |> Ash.create!() + + view2 = + PostView + |> Ash.Changeset.for_action(:create, %{browser: :chrome, post_id: post2.id}) + |> Ash.create!() + + assert [ + %{title: "aaa", views: [%{browser: :firefox}]}, + %{title: "bbb", views: [%{browser: :chrome}]} + ] = + Ash.read!( + Post + |> Ash.Query.load(:views) + |> Ash.Query.sort(title: :asc) + ) + + assert [ + %{title: "bbb", views: [%{browser: :chrome}]}, + %{title: "aaa", views: [%{browser: :firefox}]} + ] = + Ash.read!( + Post + |> Ash.Query.load(:views) + |> Ash.Query.sort({Ash.Sort.expr_sort(views.time, :datetime), :desc}, title: :asc) + ) + end end From c753a37cfe35034d6c5cf1a55704a726b01af1b0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 13 Jun 2024 09:41:13 -0400 Subject: [PATCH 0492/1215] fix: fix invalid select on sorting by some calculations --- lib/data_layer.ex | 108 ++++----------------------------------------- mix.exs | 2 +- mix.lock | 2 +- test/sort_test.exs | 16 +++---- 4 files changed, 19 insertions(+), 109 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 22589331..cc3a1dc0 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -759,7 +759,7 @@ defmodule AshPostgres.DataLayer do query, AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, resource) ) - |> remap_mapped_fields(query)} + |> AshSql.Query.remap_mapped_fields(query)} end) end rescue @@ -942,7 +942,7 @@ defmodule AshPostgres.DataLayer do path ) do {calculations_require_rewrite, aggregates_require_rewrite, query} = - rewrite_nested_selects(query) + AshSql.Query.rewrite_nested_selects(query) case lateral_join_query( query, @@ -964,7 +964,11 @@ defmodule AshPostgres.DataLayer do lateral_join_query, AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, source_resource) ) - |> remap_mapped_fields(query, calculations_require_rewrite, aggregates_require_rewrite) + |> AshSql.Query.remap_mapped_fields( + query, + calculations_require_rewrite, + aggregates_require_rewrite + ) {:ok, results} @@ -973,100 +977,6 @@ defmodule AshPostgres.DataLayer do end end - defp rewrite_nested_selects(query) do - case query.select do - %Ecto.Query.SelectExpr{ - expr: - {:merge, [], - [ - {:&, [], [0]}, - {:%{}, [], merging} - ]} - } = select -> - {merging, aggregate_merges} = remap_sub_select(merging, :aggregates) - - {new_sub_selects, calculation_merges} = - remap_sub_select(merging, :calculations) - - new_query = - %{ - query - | select: %{select | expr: {:merge, [], [{:&, [], [0]}, {:%{}, [], new_sub_selects}]}} - } - - {calculation_merges, aggregate_merges, new_query} - - _ -> - {%{}, %{}, query} - end - end - - # sobelow_skip ["DOS.StringToAtom"] - defp remap_sub_select(merging, sub_key) do - case Keyword.fetch(merging, sub_key) do - {:ok, {:%{}, [], nested}} -> - Enum.reduce(nested, {Keyword.delete(merging, sub_key), %{}}, fn {name, expr}, - {subselect, remapping} -> - new_name = String.to_atom("__#{sub_key}__#{name}") - {Keyword.put(subselect, new_name, expr), Map.put(remapping, new_name, name)} - end) - - :error -> - {merging, %{}} - end - end - - defp remap_mapped_fields( - results, - query, - calculations_require_rewrite \\ %{}, - aggregates_require_rewrite \\ %{} - ) do - calculation_names = query.__ash_bindings__.calculation_names - aggregate_names = query.__ash_bindings__.aggregate_names - - if Enum.empty?(calculation_names) and Enum.empty?(aggregate_names) and - Enum.empty?(calculations_require_rewrite) and Enum.empty?(aggregates_require_rewrite) do - results - else - Enum.map(results, fn result -> - result - |> remap_to_nested(:calculations, calculations_require_rewrite) - |> remap_to_nested(:aggregates, aggregates_require_rewrite) - |> remap(:calculations, calculation_names) - |> remap(:aggregates, aggregate_names) - end) - end - end - - defp remap_to_nested(record, _subfield, mapping) when mapping == %{} do - record - end - - defp remap_to_nested(record, subfield, mapping) do - Map.update!(record, subfield, fn subfield_values -> - Enum.reduce(mapping, subfield_values, fn {source, dest}, subfield_values -> - subfield_values - |> Map.put(dest, Map.get(record, source)) - |> Map.delete(source) - end) - end) - end - - defp remap(record, _subfield, mapping) when mapping == %{} do - record - end - - defp remap(record, subfield, mapping) do - Map.update!(record, subfield, fn subfield_values -> - Enum.reduce(mapping, subfield_values, fn {dest, source}, subfield_values -> - subfield_values - |> Map.put(dest, Map.get(subfield_values, source)) - |> Map.delete(source) - end) - end) - end - defp lateral_join_query( query, root_data, @@ -1425,7 +1335,7 @@ defmodule AshPostgres.DataLayer do end) if options[:return_records?] do - {:ok, remap_mapped_fields(results, query)} + {:ok, AshSql.Query.remap_mapped_fields(results, query)} else :ok end @@ -1691,7 +1601,7 @@ defmodule AshPostgres.DataLayer do end) if options[:return_records?] do - {:ok, remap_mapped_fields(results, query)} + {:ok, AshSql.Query.remap_mapped_fields(results, query)} else :ok end diff --git a/mix.exs b/mix.exs index e9c33c1f..5de6bcca 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0 and >= 3.0.7")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.3")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.4")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index f7ec150d..d6adb707 100644 --- a/mix.lock +++ b/mix.lock @@ -34,7 +34,7 @@ "spark": {:hex, :spark, "2.1.24", "fb596da83ee85b83f27f3a98df226869963ec5e423b4137884a6176f332e84ff", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e57f62fe1de1b327c2bfe04473496497edb6817371ba677099389deda38da90d"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "1.1.0", "ef3a7cac0f200c43caf3e6caf9be63115851b4f1cde3f21afaab220adc40e3d7", [:mix], [], "hexpm", "cccc411d5facf1bab86e7c671382d164f05f8992574c95349d3c8b317e14d953"}, + "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, diff --git a/test/sort_test.exs b/test/sort_test.exs index 88746b2d..f8301e98 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -231,20 +231,18 @@ defmodule AshPostgres.SortTest do |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0}) |> Ash.create!() - view1 = - PostView - |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post1.id}) - |> Ash.create!() + PostView + |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post1.id}) + |> Ash.create!() post2 = Post |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0}) |> Ash.create!() - view2 = - PostView - |> Ash.Changeset.for_action(:create, %{browser: :chrome, post_id: post2.id}) - |> Ash.create!() + PostView + |> Ash.Changeset.for_action(:create, %{browser: :chrome, post_id: post2.id}) + |> Ash.create!() assert [ %{title: "aaa", views: [%{browser: :firefox}]}, @@ -263,6 +261,8 @@ defmodule AshPostgres.SortTest do Ash.read!( Post |> Ash.Query.load(:views) + # this doesn't really make sense to do, you'd want to do something like `max(views, field: :time)` or something. + # but it illustrates a bug fix, and nothing currently prevents you from doing it, so we keep the test for now. |> Ash.Query.sort({Ash.Sort.expr_sort(views.time, :datetime), :desc}, title: :asc) ) end From 9cc942f233c093afefa498cc87176a5ab0d1d06d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 13 Jun 2024 09:53:40 -0400 Subject: [PATCH 0493/1215] chore: credo --- test/sort_test.exs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/sort_test.exs b/test/sort_test.exs index f8301e98..5ca9c922 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -261,8 +261,9 @@ defmodule AshPostgres.SortTest do Ash.read!( Post |> Ash.Query.load(:views) - # this doesn't really make sense to do, you'd want to do something like `max(views, field: :time)` or something. - # but it illustrates a bug fix, and nothing currently prevents you from doing it, so we keep the test for now. + # this doesn't really make sense to do, you'd want to do something like + # `max(views, field: :time)` or something. but it illustrates a bug fix, + # and nothing currently prevents you from doing it, so we keep the test for now. |> Ash.Query.sort({Ash.Sort.expr_sort(views.time, :datetime), :desc}, title: :asc) ) end From 1e29a239fd318ec5365bcc7a68ac8c5abb49885d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 13 Jun 2024 09:54:16 -0400 Subject: [PATCH 0494/1215] chore: release version v2.0.9 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b5646d..6f226053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.9](https://github.com/ash-project/ash_postgres/compare/v2.0.8...v2.0.9) (2024-06-13) + + + + +### Features: + +* autogenerate index in references (#321) + +* autogenerate index in references + +### Bug Fixes: + +* fix invalid select on sorting by some calculations + +* fix error message displaying in identity verifier + +* ensure that context multitenancy is properly applied to lateral many-to-many joins + +* don't assume old snapshots have `index?` key for attributes + +* `list_tenants` -> `all_tenants` + +* when checking for roll back-able migrations, only check `Path.basename` + +### Improvements: + +* don't sort identity keys. + ## [v2.0.8](https://github.com/ash-project/ash_postgres/compare/v2.0.7...v2.0.8) (2024-06-06) diff --git a/mix.exs b/mix.exs index 5de6bcca..15c292fa 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.8" + @version "2.0.9" def project do [ From 555ea28e219344f7de9c9c209bde67cfeeafe89e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 13 Jun 2024 09:54:34 -0400 Subject: [PATCH 0495/1215] chore: bump ash_sql version --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index d6adb707..04fe96ef 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.11", "49b06de5c533ac46cb820430d5dad9ab9f566881e7a4a400e4ba11620657b1b3", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "89c91c6f4472a199eb4e3e2f835360c85b4fd9a12cc0891c3d6feb6ec4bd4e82"}, - "ash_sql": {:hex, :ash_sql, "0.2.3", "ff2b5e61438f133cc895ed81c1fb76eac05059cd709cb307b9c9a4c9f11ffb5f", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "becbd5e527e33ed49bae7c78513e0136c5a050c143da299b8c0c735e22410c4a"}, + "ash_sql": {:hex, :ash_sql, "0.2.4", "c59c63fceedeeead4c95972bc7e247747da1e2d91c8c9052d07433545f1b9e13", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f4e97aad349607d4f837394bc1edf0bcb70dce979c91eb4b2fe97f95ad989c50"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From a3a2905c6887603facd9ece6c9d960a0759ffb06 Mon Sep 17 00:00:00 2001 From: Jechol Lee Date: Fri, 14 Jun 2024 08:54:22 +0900 Subject: [PATCH 0496/1215] Fix Elixir 1.17 warnings (#326) --- lib/mix/tasks/ash_postgres.create.ex | 2 +- lib/mix/tasks/ash_postgres.drop.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index 182360eb..e8b67469 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -36,7 +36,7 @@ defmodule Mix.Tasks.AshPostgres.Create do repos = AshPostgres.Mix.Helpers.repos!(opts, args) - |> Enum.filter(fn repo -> repo.create? end) + |> Enum.filter(fn repo -> repo.create?() end) repo_args = Enum.flat_map(repos, fn repo -> diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 36669f99..5180d1d3 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -46,7 +46,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do repos = AshPostgres.Mix.Helpers.repos!(opts, args) - |> Enum.filter(fn repo -> repo.drop? end) + |> Enum.filter(fn repo -> repo.drop?() end) repo_args = Enum.flat_map(repos, fn repo -> From da4aae676e8b35a5dfda163e19b39cacc5f9660a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:50:02 -0400 Subject: [PATCH 0497/1215] chore(deps): bump ecto_sql from 3.11.2 to 3.11.3 (#327) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 04fe96ef..6cba78f5 100644 --- a/mix.lock +++ b/mix.lock @@ -11,7 +11,7 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.2", "c7cc7f812af571e50b80294dc2e535821b3b795ce8008d07aa5f336591a185a8", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "73c07f995ac17dbf89d3cfaaf688fcefabcd18b7b004ac63b0dc4ef39499ed6b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, From c3190fe49ca4c241c258ae546211bf7a33f5681d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:50:14 -0400 Subject: [PATCH 0498/1215] chore(deps): bump ash_sql from 0.2.4 to 0.2.5 (#328) --- mix.lock | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 6cba78f5..01151ebc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,9 @@ %{ "ash": {:hex, :ash, "3.0.11", "49b06de5c533ac46cb820430d5dad9ab9f566881e7a4a400e4ba11620657b1b3", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "89c91c6f4472a199eb4e3e2f835360c85b4fd9a12cc0891c3d6feb6ec4bd4e82"}, - "ash_sql": {:hex, :ash_sql, "0.2.4", "c59c63fceedeeead4c95972bc7e247747da1e2d91c8c9052d07433545f1b9e13", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f4e97aad349607d4f837394bc1edf0bcb70dce979c91eb4b2fe97f95ad989c50"}, + "ash_sql": {:hex, :ash_sql, "0.2.5", "8b50c3178776263b912e1b60e161e2bcf08a907a38abf703edf8a8a0a51b3fe2", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0d5d8606738a17c4e8c0be4244623df721abee5072cee69d31c2711c36d0548f"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, @@ -17,21 +18,31 @@ "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, + "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, + "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, + "igniter": {:hex, :igniter, "0.1.6", "d3b70d99a18020b32a3dff11a5a9f5ad9cd9d89bb375d9eb9d677e80d0f71e9a", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}], "hexpm", "e9081342f49e989eb06dbafa75a803df294faf9eaedfa934cd17d5583275297f"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, + "req": {:hex, :req, "0.5.0", "6d8a77c25cfc03e06a439fb12ffb51beade53e3fe0e2c5e362899a18b50298b3", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "dda04878c1396eebbfdec6db6f3d4ca609e5c8846b7ee88cc56eb9891406f7a3"}, + "rewrite": {:hex, :rewrite, "0.10.4", "09e3a18e7e2d2ad0875afa9d27d9b60efd156c0f2f9f0ef2ff585504bc378efb", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "93cd0ab5441d77413d569567f7430844e2d10484b139aa5326786ad4d0481ef0"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"}, - "spark": {:hex, :spark, "2.1.24", "fb596da83ee85b83f27f3a98df226869963ec5e423b4137884a6176f332e84ff", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e57f62fe1de1b327c2bfe04473496497edb6817371ba677099389deda38da90d"}, + "spark": {:hex, :spark, "2.2.0", "82fd4cb7808ba462d73d3ce53a7f85fe067060d8217b9cded4301b2e8de1f1e9", [:mix], [{:igniter, "~> 0.1", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "4d0cc00bf8b68ab756d7d38a4f862556aa3df9120c6294cd1a77a9d67bb5349a"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, From 0db1b29c239db41ad6ce3e2d8e520dbd0097a172 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 09:04:09 -0400 Subject: [PATCH 0499/1215] chore(deps): bump ash from 3.0.11 to 3.0.12 (#329) Bumps [ash](https://github.com/ash-project/ash) from 3.0.11 to 3.0.12. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.0.11...v3.0.12) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 01151ebc..d8180009 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.11", "49b06de5c533ac46cb820430d5dad9ab9f566881e7a4a400e4ba11620657b1b3", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "89c91c6f4472a199eb4e3e2f835360c85b4fd9a12cc0891c3d6feb6ec4bd4e82"}, + "ash": {:hex, :ash, "3.0.12", "c5b8f8884dab1ab140f11ff6e5a5a3889eae6c5083cabc1952d65fb2a6d13b89", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b8c9b56784f71debda4fc23960ecb6645b39eaa26376ba0423b94c550632c07b"}, "ash_sql": {:hex, :ash_sql, "0.2.5", "8b50c3178776263b912e1b60e161e2bcf08a907a38abf703edf8a8a0a51b3fe2", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0d5d8606738a17c4e8c0be4244623df721abee5072cee69d31c2711c36d0548f"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, - "igniter": {:hex, :igniter, "0.1.6", "d3b70d99a18020b32a3dff11a5a9f5ad9cd9d89bb375d9eb9d677e80d0f71e9a", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}], "hexpm", "e9081342f49e989eb06dbafa75a803df294faf9eaedfa934cd17d5583275297f"}, + "igniter": {:hex, :igniter, "0.1.7", "dd5674a6ec2ec1728a690d4915a5a430ba213b45cad86f5710cf9076e6274d0e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.3", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.1 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "08d95b91fee280be305887b128dcf10a4ff55a8ab72a452489b1529e8644a8c8"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -38,11 +38,12 @@ "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, "req": {:hex, :req, "0.5.0", "6d8a77c25cfc03e06a439fb12ffb51beade53e3fe0e2c5e362899a18b50298b3", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "dda04878c1396eebbfdec6db6f3d4ca609e5c8846b7ee88cc56eb9891406f7a3"}, - "rewrite": {:hex, :rewrite, "0.10.4", "09e3a18e7e2d2ad0875afa9d27d9b60efd156c0f2f9f0ef2ff585504bc378efb", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "93cd0ab5441d77413d569567f7430844e2d10484b139aa5326786ad4d0481ef0"}, + "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"}, - "spark": {:hex, :spark, "2.2.0", "82fd4cb7808ba462d73d3ce53a7f85fe067060d8217b9cded4301b2e8de1f1e9", [:mix], [{:igniter, "~> 0.1", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "4d0cc00bf8b68ab756d7d38a4f862556aa3df9120c6294cd1a77a9d67bb5349a"}, + "sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"}, + "spark": {:hex, :spark, "2.2.1", "b4ec56c99b3a750f7db0d2651fc59dc59c4002a5e0d658dde968fc69bd451136", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a6052f08ebe1ef152f86489151401210755bd81a9f0cbd737dd50819c65a0fc1"}, + "spitfire": {:hex, :spitfire, "0.1.1", "249c8ea38d9e313e636670f2f692df5158b231e5986cbd13390498741d33cc0b", [:mix], [], "hexpm", "13782a1f902bfa52f5058d71752f4aa78c6056667a66bda5804480b7355e3aa9"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, From 97e538bf63970748113d82d5aa5881a39c3d3809 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Jun 2024 11:38:28 -0400 Subject: [PATCH 0500/1215] improvement: identities w/ calculations and where clauses in upserts --- lib/data_layer.ex | 80 +++++++++++++++++++++++++++++------ mix.exs | 2 +- mix.lock | 2 +- test/unique_identity_test.exs | 22 ++++++++++ 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index cc3a1dc0..7649e27d 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1721,6 +1721,7 @@ defmodule AshPostgres.DataLayer do :conflict_target, conflict_target( resource, + options[:identity], options[:upsert_keys] || Ash.Resource.Info.primary_key(resource) ) ) @@ -2469,7 +2470,7 @@ defmodule AshPostgres.DataLayer do end @impl true - def upsert(resource, changeset, keys \\ nil) do + def upsert(resource, changeset, keys, identity) do if AshPostgres.DataLayer.Info.manage_tenant_update?(resource) do {:error, "Cannot currently upsert a resource that owns a tenant"} else @@ -2491,6 +2492,7 @@ defmodule AshPostgres.DataLayer do single?: true, upsert?: true, tenant: changeset.tenant, + identity: identity, upsert_keys: keys, upsert_fields: upsert_fields, return_records?: true @@ -2504,25 +2506,75 @@ defmodule AshPostgres.DataLayer do end end - defp conflict_target(resource, keys) do - if Ash.Resource.Info.base_filter(resource) do - base_filter_sql = - AshPostgres.DataLayer.Info.base_filter_sql(resource) || - raise """ - Cannot use upserts with resources that have a base_filter without also adding `base_filter_sql` in the postgres section. - """ + defp conflict_target(resource, identity, keys) do + identity_where = + case identity do + %{name: name, where: where} when not is_nil(where) -> + AshPostgres.DataLayer.Info.identity_where_to_sql(resource, name) || + raise( + "Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql` to use it as an upsert_identity" + ) - sources = - Enum.map(keys, fn key -> - ~s("#{Ash.Resource.Info.attribute(resource, key).source || key}") - end) + _ -> + nil + end - {:unsafe_fragment, "(" <> Enum.join(sources, ", ") <> ") WHERE (#{base_filter_sql})"} - else + base_filter_sql = + case Ash.Resource.Info.base_filter(resource) do + nil -> + nil + + _base_filter -> + AshPostgres.DataLayer.Info.base_filter_sql(resource) || + raise """ + Cannot use upserts with resources that have a base_filter without also adding `base_filter_sql` in the postgres section. + """ + end + + where = + case {base_filter_sql, identity_where} do + {nil, nil} -> + nil + + {base_filter_sql, nil} -> + " WHERE (#{base_filter_sql})" + + {nil, identity_where} -> + " WHERE (#{identity_where})" + + {base_filter_sql, identity_where} -> + " WHERE ((#{base_filter_sql}) AND (#{identity_where}))" + end + + if is_nil(where) && Enum.all?(keys, &Ash.Resource.Info.attribute(resource, &1)) do keys + else + sources = sources_to_sql(resource, keys) + {:unsafe_fragment, "(" <> Enum.join(sources, ", ") <> ")#{where}"} end end + defp sources_to_sql(resource, keys) do + Enum.map(keys, fn key -> + case Ash.Resource.Info.field(resource, key) do + %Ash.Resource.Attribute{source: source, name: name} -> + ~s("#{source || name}") + + %Ash.Resource.Calculation{name: name} -> + if sql = AshPostgres.DataLayer.Info.calculations_to_sql(name) do + "(" <> sql <> ")" + else + raise ArgumentError, + "Calculation #{inspect(key)} used in `AshPostgres.DataLayer` conflict target must have its sql defined in `calculations_to_sql`" + end + + _other -> + raise ArgumentError, + "Unsupported field #{inspect(key)} used in `AshPostgres.DataLayer` conflict target" + end + end) + end + defp update_defaults(resource) do attributes = resource diff --git a/mix.exs b/mix.exs index 15c292fa..11369d93 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0 and >= 3.0.7")}, + {:ash, ash_version("~> 3.0 and >= 3.0.13")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.4")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index d8180009..70113673 100644 --- a/mix.lock +++ b/mix.lock @@ -43,7 +43,7 @@ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"}, "spark": {:hex, :spark, "2.2.1", "b4ec56c99b3a750f7db0d2651fc59dc59c4002a5e0d658dde968fc69bd451136", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a6052f08ebe1ef152f86489151401210755bd81a9f0cbd737dd50819c65a0fc1"}, - "spitfire": {:hex, :spitfire, "0.1.1", "249c8ea38d9e313e636670f2f692df5158b231e5986cbd13390498741d33cc0b", [:mix], [], "hexpm", "13782a1f902bfa52f5058d71752f4aa78c6056667a66bda5804480b7355e3aa9"}, + "spitfire": {:hex, :spitfire, "0.1.2", "49b85d59c170d671e7e49649f62f6fe0771743a61bc42bd7a203f98f322d99e2", [:mix], [], "hexpm", "21f04cf02df601d75e1551e393ee5c927e3986fbb7598f3e59f71d4ca544fd9b"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, diff --git a/test/unique_identity_test.exs b/test/unique_identity_test.exs index e2d1f9a4..e210ac77 100644 --- a/test/unique_identity_test.exs +++ b/test/unique_identity_test.exs @@ -42,4 +42,26 @@ defmodule AshPostgres.Test.UniqueIdentityTest do assert new_post.id == post.id assert new_post.price == 10 end + + test "a unique constraint can be used to upsert when backed by a calculation" do + post = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "title", + uniq_if_contains_foo: "abcfoodef", + price: 10 + }) + |> Ash.create!() + + new_post = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "title2", + uniq_if_contains_foo: "abcfoodef" + }) + |> Ash.create!(upsert?: true, upsert_identity: :uniq_if_contains_foo) + + assert new_post.id == post.id + assert new_post.price == 10 + end end From 54c88a882c2067dbec20a4d1e363d2799ee91593 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Jun 2024 15:07:05 -0400 Subject: [PATCH 0501/1215] fix: properly get calculation sql --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 7649e27d..15be6469 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2561,7 +2561,7 @@ defmodule AshPostgres.DataLayer do ~s("#{source || name}") %Ash.Resource.Calculation{name: name} -> - if sql = AshPostgres.DataLayer.Info.calculations_to_sql(name) do + if sql = AshPostgres.DataLayer.Info.calculation_to_sql(resource, name) do "(" <> sql <> ")" else raise ArgumentError, From 2287b45a59a37b08c9aac4ea5e001f4d681d4b52 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Jun 2024 15:26:04 -0400 Subject: [PATCH 0502/1215] chore: don't wrap the sql in parens --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 15be6469..99a69618 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2562,7 +2562,7 @@ defmodule AshPostgres.DataLayer do %Ash.Resource.Calculation{name: name} -> if sql = AshPostgres.DataLayer.Info.calculation_to_sql(resource, name) do - "(" <> sql <> ")" + sql else raise ArgumentError, "Calculation #{inspect(key)} used in `AshPostgres.DataLayer` conflict target must have its sql defined in `calculations_to_sql`" From cacf71cd550aebad282944b2742a6995399e6664 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Jun 2024 15:34:05 -0400 Subject: [PATCH 0503/1215] fix: ensure that parens are always added to calculation generated SQL --- lib/data_layer.ex | 2 +- .../migration_generator.ex | 10 +- .../test_repo/post_links/20240617193218.json | 100 +++++ .../test_repo/posts/20240617193218.json | 398 ++++++++++++++++++ .../20240617193218_migrate_resources28.exs | 59 +++ 5 files changed, 564 insertions(+), 5 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/post_links/20240617193218.json create mode 100644 priv/resource_snapshots/test_repo/posts/20240617193218.json create mode 100644 priv/test_repo/migrations/20240617193218_migrate_resources28.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 99a69618..15be6469 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2562,7 +2562,7 @@ defmodule AshPostgres.DataLayer do %Ash.Resource.Calculation{name: name} -> if sql = AshPostgres.DataLayer.Info.calculation_to_sql(resource, name) do - sql + "(" <> sql <> ")" else raise ArgumentError, "Calculation #{inspect(key)} used in `AshPostgres.DataLayer` conflict target must have its sql defined in `calculations_to_sql`" diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7c51e9ea..2d96b5d5 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2887,11 +2887,13 @@ defmodule AshPostgres.MigrationGenerator do to_string(attribute.source || attribute.name) %Ash.Resource.Calculation{} -> - AshPostgres.DataLayer.Info.calculation_to_sql(resource, key) || - raise "Must define an entry for :#{key} in `postgres.calculations_to_sql`, or skip this identity with `postgres.skip_unique_indexes`" + sql = + AshPostgres.DataLayer.Info.calculation_to_sql(resource, key) || + raise "Must define an entry for :#{key} in `postgres.calculations_to_sql`, or skip this identity with `postgres.skip_unique_indexes`" + + "(" <> sql <> ")" end - end) - |> Enum.sort(), + end), where: if identity.where do AshPostgres.DataLayer.Info.identity_where_to_sql(resource, identity.name) || diff --git a/priv/resource_snapshots/test_repo/post_links/20240617193218.json b/priv/resource_snapshots/test_repo/post_links/20240617193218.json new file mode 100644 index 00000000..15c08a07 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20240617193218.json @@ -0,0 +1,100 @@ +{ + "attributes": [ + { + "default": "\"active\"", + "size": null, + "type": "text", + "source": "state", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "source_post_id", + "references": { + "name": "post_links_source_post_id_fkey", + "table": "posts", + "schema": "public", + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "on_update": null, + "on_delete": null, + "deferrable": false, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "match_with": null, + "match_type": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "destination_post_id", + "references": { + "name": "post_links_destination_post_id_fkey", + "table": "posts", + "schema": "public", + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "on_update": null, + "on_delete": null, + "deferrable": false, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "match_with": null, + "match_type": null + }, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + } + ], + "table": "post_links", + "hash": "3725FF19BB06D2840E2ADCAE060458215E8726EF0D855BB4898A66086137BD53", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "identities": [ + { + "name": "unique_link", + "keys": [ + "source_post_id", + "destination_post_id" + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "base_filter": null, + "index_name": "post_links_unique_link_index" + } + ], + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "base_filter": null, + "custom_indexes": [], + "custom_statements": [], + "check_constraints": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20240617193218.json b/priv/resource_snapshots/test_repo/posts/20240617193218.json new file mode 100644 index 00000000..a687c9b8 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240617193218.json @@ -0,0 +1,398 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "timestamptz(6)", + "source": "datetime", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_on_upper", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_if_contains_foo", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "timestamptz(6)", + "source": "updated_at", + "references": null, + "primary_key?": false, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "on_update": null, + "on_delete": null, + "deferrable": false, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "destination_attribute": "id", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "on_update": null, + "on_delete": null, + "deferrable": false, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null, + "match_with": null, + "match_type": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "posts", + "hash": "C7F77A2A5EEB68BD76CA1CCA3F871489E335DEB7887D17E8C300DB90E27F5545", + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "identities": [ + { + "name": "uniq_if_contains_foo", + "keys": [ + "uniq_if_contains_foo" + ], + "where": "(uniq_if_contains_foo LIKE '%foo%')", + "nils_distinct?": true, + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index" + }, + { + "name": "uniq_on_upper", + "keys": [ + "(UPPER(uniq_on_upper))" + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index" + }, + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "where": null, + "nils_distinct?": true, + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index" + } + ], + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "base_filter": "type = 'sponsored'", + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "where": null, + "unique": true, + "using": null, + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "nulls_distinct": true + } + ], + "custom_statements": [], + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240617193218_migrate_resources28.exs b/priv/test_repo/migrations/20240617193218_migrate_resources28.exs new file mode 100644 index 00000000..d112c67a --- /dev/null +++ b/priv/test_repo/migrations/20240617193218_migrate_resources28.exs @@ -0,0 +1,59 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources28 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 + drop_if_exists( + unique_index(:posts, ["UPPER(uniq_on_upper)"], name: "posts_uniq_on_upper_index") + ) + + create( + unique_index(:posts, ["(UPPER(uniq_on_upper))"], + where: "(type = 'sponsored')", + name: "posts_uniq_on_upper_index" + ) + ) + + drop_if_exists( + unique_index(:post_links, ["destination_post_id", "source_post_id"], + name: "post_links_unique_link_index" + ) + ) + + create( + unique_index(:post_links, ["source_post_id", "destination_post_id"], + name: "post_links_unique_link_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:post_links, ["source_post_id", "destination_post_id"], + name: "post_links_unique_link_index" + ) + ) + + create( + unique_index(:post_links, ["destination_post_id", "source_post_id"], + name: "post_links_unique_link_index" + ) + ) + + drop_if_exists( + unique_index(:posts, ["(UPPER(uniq_on_upper))"], name: "posts_uniq_on_upper_index") + ) + + create( + unique_index(:posts, ["UPPER(uniq_on_upper)"], + where: "type = 'sponsored'", + name: "posts_uniq_on_upper_index" + ) + ) + end +end From 14938f9f475b7fbc43c45b4e1e39098d31818452 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 18 Jun 2024 12:23:27 +0200 Subject: [PATCH 0504/1215] test: add test to show error with building a reference (#330) --- test/atomics_test.exs | 7 +++++++ test/support/resources/post.ex | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 5a83521f..71dedead 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -198,6 +198,13 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() assert post.title == "John" + + post = + post + |> Ash.Changeset.for_update(:set_attributes_from_parent, %{}) + |> Ash.update!() + + assert post.title == "John" end test "relationships can be used in atomic update and in an atomic update filter" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 7f8781e7..15168130 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -134,6 +134,23 @@ defmodule AshPostgres.Test.Post do end) end + update :set_attributes_from_parent do + require_atomic?(false) + + change( + atomic_update( + :title, + expr( + if is_nil(parent_post_id) do + author.title + else + parent_post.author.title + end + ) + ) + ) + end + update :update do primary?(true) require_atomic?(false) @@ -273,6 +290,10 @@ defmodule AshPostgres.Test.Post do filter(expr(^actor(:id) == id)) end + belongs_to :parent_post, __MODULE__ do + public?(true) + end + belongs_to(:author, AshPostgres.Test.Author) do public?(true) end From 860d08538659657db0b08049c06506de2fee73ac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Jun 2024 07:50:54 -0400 Subject: [PATCH 0505/1215] fix: update ash_sql to fix query generation issues --- mix.exs | 2 +- mix.lock | 4 +- .../test_repo/posts/20240618102809.json | 427 ++++++++++++++++++ .../20240618102809_migrate_resources29.exs | 31 ++ test/atomics_test.exs | 16 +- test/migration_generator_test.exs | 2 +- test/support/resources/post.ex | 2 +- 7 files changed, 477 insertions(+), 7 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20240618102809.json create mode 100644 priv/test_repo/migrations/20240618102809_migrate_resources29.exs diff --git a/mix.exs b/mix.exs index 11369d93..47f8977c 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0 and >= 3.0.13")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.4")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.6")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 70113673..21d65d6a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.12", "c5b8f8884dab1ab140f11ff6e5a5a3889eae6c5083cabc1952d65fb2a6d13b89", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b8c9b56784f71debda4fc23960ecb6645b39eaa26376ba0423b94c550632c07b"}, + "ash": {:hex, :ash, "3.0.13", "9111fa58362f82fd6687635c12ea96ce1958d24772e3c773fbc8b0893a7e7d47", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b5c1a9c428eac939a313bfea5e4509e8ac39beaa4707bd0deb5f7f310b1022c3"}, "ash_sql": {:hex, :ash_sql, "0.2.5", "8b50c3178776263b912e1b60e161e2bcf08a907a38abf703edf8a8a0a51b3fe2", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0d5d8606738a17c4e8c0be4244623df721abee5072cee69d31c2711c36d0548f"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -42,7 +42,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"}, - "spark": {:hex, :spark, "2.2.1", "b4ec56c99b3a750f7db0d2651fc59dc59c4002a5e0d658dde968fc69bd451136", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a6052f08ebe1ef152f86489151401210755bd81a9f0cbd737dd50819c65a0fc1"}, + "spark": {:hex, :spark, "2.2.3", "d41c45f0d7e8289499bf104173543be41ee1d8d213cb7503e9328b30751f2f13", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "401dec157c8b88634c27f241f4e374476081c577f51d2b08512f2c681c25a852"}, "spitfire": {:hex, :spitfire, "0.1.2", "49b85d59c170d671e7e49649f62f6fe0771743a61bc42bd7a203f98f322d99e2", [:mix], [], "hexpm", "21f04cf02df601d75e1551e393ee5c927e3986fbb7598f3e59f71d4ca544fd9b"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/priv/resource_snapshots/test_repo/posts/20240618102809.json b/priv/resource_snapshots/test_repo/posts/20240618102809.json new file mode 100644 index 00000000..9ea77114 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240618102809.json @@ -0,0 +1,427 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "title_column", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "timestamptz(6)", + "source": "datetime", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "score", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "boolean", + "source": "public", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "citext", + "source": "category", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"sponsored\"", + "size": null, + "type": "text", + "source": "type", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "price", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"0\"", + "size": null, + "type": "decimal", + "source": "decimal", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "status", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "status", + "source": "status_enum", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "float" + ], + "source": "point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "custom_point", + "source": "composite_point", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "map", + "source": "stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "map" + ], + "source": "list_of_stuff", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_one", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_custom_two", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_on_upper", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "uniq_if_contains_foo", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": [ + "array", + "text" + ], + "source": "list_containing_nils", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "utc_datetime_usec", + "source": "created_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "size": null, + "type": "timestamptz(6)", + "source": "updated_at", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "organization_id", + "references": { + "name": "posts_organization_id_fkey", + "table": "orgs", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "parent_post_id", + "references": { + "name": "posts_parent_post_id_fkey", + "table": "posts", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "author_id", + "references": { + "name": "posts_author_id_fkey", + "table": "authors", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + } + ], + "table": "posts", + "hash": "1E9B5F3F36E68AC20FDA61AC5AC645F86FCD61DA2EBD0C2716A91AE072737C25", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "uniq_if_contains_foo", + "keys": [ + "uniq_if_contains_foo" + ], + "where": "(uniq_if_contains_foo LIKE '%foo%')", + "base_filter": "type = 'sponsored'", + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "posts_uniq_if_contains_foo_index" + }, + { + "name": "uniq_on_upper", + "keys": [ + "(UPPER(uniq_on_upper))" + ], + "where": null, + "base_filter": "type = 'sponsored'", + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "posts_uniq_on_upper_index" + }, + { + "name": "uniq_one_and_two", + "keys": [ + "uniq_one", + "uniq_two" + ], + "where": null, + "base_filter": "type = 'sponsored'", + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "posts_uniq_one_and_two_index" + } + ], + "schema": null, + "check_constraints": [ + { + "name": "price_must_be_positive", + "check": "price > 0", + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'" + } + ], + "custom_indexes": [ + { + "message": "dude what the heck", + "name": null, + "table": null, + "include": null, + "where": null, + "prefix": null, + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "unique": true, + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "nulls_distinct": true, + "using": null + } + ], + "base_filter": "type = 'sponsored'", + "multitenancy": { + "global": null, + "strategy": null, + "attribute": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240618102809_migrate_resources29.exs b/priv/test_repo/migrations/20240618102809_migrate_resources29.exs new file mode 100644 index 00000000..5a4aa79b --- /dev/null +++ b/priv/test_repo/migrations/20240618102809_migrate_resources29.exs @@ -0,0 +1,31 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources29 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(:posts) do + add( + :parent_post_id, + references(:posts, + column: :id, + name: "posts_parent_post_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + end + + def down do + drop(constraint(:posts, "posts_parent_post_id_fkey")) + + alter table(:posts) do + remove(:parent_post_id) + end + end +end diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 71dedead..95d4712c 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -184,12 +184,24 @@ defmodule AshPostgres.AtomicsTest do test "relationships can be used in atomic update" do author = Author - |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.Changeset.for_create(:create, %{ + first_name: "John", + last_name: "Doe" + }) + |> Ash.create!() + + parent_post = + Post + |> Ash.Changeset.for_create(:create, %{price: 42, author_id: author.id}) |> Ash.create!() post = Post - |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) + |> Ash.Changeset.for_create(:create, %{ + price: 1, + author_id: author.id, + parent_post_id: parent_post.id + }) |> Ash.create!() post = diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 2b97ebcd..1a323202 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -160,7 +160,7 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration creates unique_indexes based on the identities of the resource assert file_contents =~ - ~S{create unique_index(:posts, ["second_title", "title"], name: "posts_thing_index")} + ~S{create unique_index(:posts, ["title", "second_title"], name: "posts_thing_index")} # the migration creates unique_indexes using the `source` on the attributes of the identity on the resource assert file_contents =~ diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 15168130..9bb77c0d 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -144,7 +144,7 @@ defmodule AshPostgres.Test.Post do if is_nil(parent_post_id) do author.title else - parent_post.author.title + parent_post.author.first_name end ) ) From 2a1bc993eb3cac8bb0475f14d662f6c0c07fceb3 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 18 Jun 2024 15:03:24 +0200 Subject: [PATCH 0506/1215] test: update tests to show many_to_many filter problem (#309) --- .../20240618085942.json | 121 ++++++++++++++++++ .../20240618085942_migrate_resources29.exs | 59 +++++++++ test/load_test.exs | 31 ++++- test/support/domain.ex | 1 + test/support/resources/post.ex | 21 +++ .../resources/stateful_post_follwer.ex | 44 +++++++ 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json create mode 100644 priv/test_repo/migrations/20240618085942_migrate_resources29.exs create mode 100644 test/support/resources/stateful_post_follwer.ex diff --git a/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json b/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json new file mode 100644 index 00000000..3f872e31 --- /dev/null +++ b/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json @@ -0,0 +1,121 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "allow_nil?": false, + "generated?": false, + "primary_key?": true + }, + { + "default": "nil", + "size": null, + "type": "bigint", + "source": "order", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "\"active\"", + "size": null, + "type": "text", + "source": "state", + "references": null, + "allow_nil?": true, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "post_id", + "references": { + "name": "stateful_post_followers_post_id_fkey", + "table": "posts", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "follower_id", + "references": { + "name": "stateful_post_followers_follower_id_fkey", + "table": "users", + "schema": "public", + "on_delete": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "primary_key?": true, + "destination_attribute": "id", + "on_update": null, + "deferrable": false, + "index?": false, + "match_type": null, + "match_with": null, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "allow_nil?": false, + "generated?": false, + "primary_key?": false + } + ], + "table": "stateful_post_followers", + "hash": "AD29C60B91FF00BAC638481D15457BA12C9D2E01B7B3B7F30F8E19DED6534C09", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "join_attributes", + "keys": [ + "post_id", + "follower_id", + "state" + ], + "where": null, + "base_filter": null, + "nils_distinct?": true, + "all_tenants?": false, + "index_name": "stateful_post_followers_join_attributes_index" + } + ], + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "multitenancy": { + "global": null, + "attribute": null, + "strategy": null + }, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240618085942_migrate_resources29.exs b/priv/test_repo/migrations/20240618085942_migrate_resources29.exs new file mode 100644 index 00000000..f8e6fe26 --- /dev/null +++ b/priv/test_repo/migrations/20240618085942_migrate_resources29.exs @@ -0,0 +1,59 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources29 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 + create table(:stateful_post_followers, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:order, :bigint) + add(:state, :text, default: "active") + + add( + :post_id, + references(:posts, + column: :id, + name: "stateful_post_followers_post_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + ) + + add( + :follower_id, + references(:users, + column: :id, + name: "stateful_post_followers_follower_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + ) + end + + create( + unique_index(:stateful_post_followers, ["post_id", "follower_id", "state"], + name: "stateful_post_followers_join_attributes_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:stateful_post_followers, ["post_id", "follower_id", "state"], + name: "stateful_post_followers_join_attributes_index" + ) + ) + + drop(constraint(:stateful_post_followers, "stateful_post_followers_post_id_fkey")) + + drop(constraint(:stateful_post_followers, "stateful_post_followers_follower_id_fkey")) + + drop(table(:stateful_post_followers)) + end +end diff --git a/test/load_test.exs b/test/load_test.exs index ffc7fc2b..200e1f29 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Post, Record, TempEntity, User} + alias AshPostgres.Test.{Author, Comment, Post, Record, TempEntity, User, StatefulPostFollower} require Ash.Query @@ -74,6 +74,35 @@ defmodule AshPostgres.Test.LoadTest do assert %{linked_posts: [%{title: "destination"}, %{title: "destination"}]} = results end + test "many_to_many loads work with filter on join relationship" do + followers = + for i <- 0..2 do + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + end + + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.Changeset.manage_relationship(:stateful_followers, followers, type: :append_and_remove) + |> Ash.create!() + + StatefulPostFollower + |> Ash.Query.for_read(:read, %{}) + |> Ash.Query.limit(1) + |> Ash.read_one!() + |> Ash.Changeset.for_update(:update, %{state: :inactive}) + |> Ash.update!() + + [post] = + Post + |> Ash.Query.for_read(:read, %{}) + |> Ash.Query.load(:active_followers) + |> Ash.read!() + + assert length(post.active_followers) == 2 + end + test "many_to_many loads work when nested" do source_post = Post diff --git a/test/support/domain.ex b/test/support/domain.ex index 9b7e9057..e322c204 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -19,6 +19,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.TempEntity) resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) + resource(AshPostgres.Test.StatefulPostFollower) end authorization do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 9bb77c0d..c59dcfaf 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -355,6 +355,27 @@ defmodule AshPostgres.Test.Post do read_action: :active ) + has_many :active_followers_assoc, AshPostgres.Test.StatefulPostFollower do + public?(true) + filter(expr(state == :active)) + end + + many_to_many(:active_followers, AshPostgres.Test.User, + public?: true, + through: AshPostgres.Test.StatefulPostFollower, + join_relationship: :active_followers_assoc, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :follower_id + ) + + many_to_many(:stateful_followers, AshPostgres.Test.User, + public?: true, + through: AshPostgres.Test.StatefulPostFollower, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :follower_id, + read_action: :active + ) + has_many(:post_followers, AshPostgres.Test.PostFollower) many_to_many(:sorted_followers, AshPostgres.Test.User, diff --git a/test/support/resources/stateful_post_follwer.ex b/test/support/resources/stateful_post_follwer.ex new file mode 100644 index 00000000..b6fd8dab --- /dev/null +++ b/test/support/resources/stateful_post_follwer.ex @@ -0,0 +1,44 @@ +defmodule AshPostgres.Test.StatefulPostFollower do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "stateful_post_followers" + repo AshPostgres.TestRepo + end + + identities do + identity(:join_attributes, [:post_id, :follower_id, :state]) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + attribute(:order, :integer, public?: true) + + attribute :state, :atom do + public?(true) + constraints(one_of: [:active, :inactive]) + default(:active) + end + end + + relationships do + belongs_to :post, AshPostgres.Test.Post do + public?(true) + allow_nil?(false) + end + + belongs_to :follower, AshPostgres.Test.User do + public?(true) + allow_nil?(false) + end + end +end From 846a8fca069ac513358d662a78da06dfd0173849 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Jun 2024 09:15:49 -0400 Subject: [PATCH 0507/1215] chore: fix migration code --- ...ate_resources29.exs => 20240618102809_migrate_resources30.exs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename priv/test_repo/migrations/{20240618102809_migrate_resources29.exs => 20240618102809_migrate_resources30.exs} (100%) diff --git a/priv/test_repo/migrations/20240618102809_migrate_resources29.exs b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs similarity index 100% rename from priv/test_repo/migrations/20240618102809_migrate_resources29.exs rename to priv/test_repo/migrations/20240618102809_migrate_resources30.exs From 526020e26cc0917d8f94c4929ffa2dfb1bbeb348 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Jun 2024 16:37:32 -0400 Subject: [PATCH 0508/1215] improvement: better type handling using new type inference --- .credo.exs | 2 +- lib/sql_implementation.ex | 67 ++++++++++++++++++++++--------- mix.exs | 2 +- mix.lock | 2 +- test/load_test.exs | 2 +- test/migration_generator_test.exs | 2 +- 6 files changed, 53 insertions(+), 24 deletions(-) diff --git a/.credo.exs b/.credo.exs index 9442b952..fa9affad 100644 --- a/.credo.exs +++ b/.credo.exs @@ -123,7 +123,7 @@ {Credo.Check.Refactor.MatchInCondition, []}, {Credo.Check.Refactor.NegatedConditionsInUnless, []}, {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, [max_nesting: 5]}, + {Credo.Check.Refactor.Nesting, [max_nesting: 7]}, {Credo.Check.Refactor.UnlessWithElse, []}, {Credo.Check.Refactor.WithClauses, []}, diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 57e973ec..d037d2d9 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -229,8 +229,10 @@ defmodule AshPostgres.SqlImplementation do true -> [:any] end - |> Enum.concat(Map.keys(Ash.Query.Operator.operator_overloads(name) || %{})) - |> Enum.map(fn types -> + |> then(fn types -> + Enum.concat(Map.keys(Ash.Query.Operator.operator_overloads(name) || %{}), types) + end) + |> Enum.flat_map(fn types -> case types do :same -> types = @@ -238,32 +240,58 @@ defmodule AshPostgres.SqlImplementation do :same end - closest_fitting_type(types, values) + [closest_fitting_type(types, values)] :any -> - for _ <- values do - :any - end + [] types -> - closest_fitting_type(types, values) + [types] end end) + # this doesn't seem right to me |> Enum.filter(fn types -> Enum.all?(types, &(vagueness(&1) == 0)) end) |> case do - [type] -> - if type == :any || type == {:in, :any} do - nil - else - type - end - - # There are things we could likely do here - # We only say "we know what types these are" when we explicitly know - _ -> - Enum.map(values, fn _ -> nil end) + [types] -> + types + + types -> + Enum.find_value(types, Enum.map(values, fn _ -> nil end), fn types -> + if length(types) == length(values) do + types + |> Enum.zip(values) + |> Enum.reduce_while([], fn {type, value}, vals -> + type = Ash.Type.get_type(type) + # this means its a known type + if Ash.Type.ash_type?(type) do + {type, constraints} = + case type do + {type, constraints} -> {type, constraints} + type -> {type, []} + end + + case value do + %Ash.Query.Function.Type{arguments: [_, ^type | _]} -> + {:cont, vals ++ [:any]} + + %Ash.Query.Ref{attribute: %{type: ^type}} -> + {:cont, vals ++ [:any]} + + _ -> + if Ash.Type.matches_type?(type, value, constraints) do + {:cont, vals ++ [parameterized_type(type, constraints)]} + else + {:halt, nil} + end + end + else + {:halt, nil} + end + end) + end + end) end end @@ -370,7 +398,8 @@ defmodule AshPostgres.SqlImplementation do end end - defp fill_in_known_type({type, value}), do: {array_to_in(type), value} + defp fill_in_known_type({type, value}), + do: {array_to_in(type), value} defp array_to_in({:array, v}), do: {:in, array_to_in(v)} diff --git a/mix.exs b/mix.exs index 47f8977c..e53450e2 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0 and >= 3.0.13")}, + {:ash, ash_version("~> 3.0 and >= 3.0.15")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.6")}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index 21d65d6a..560cd842 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.13", "9111fa58362f82fd6687635c12ea96ce1958d24772e3c773fbc8b0893a7e7d47", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b5c1a9c428eac939a313bfea5e4509e8ac39beaa4707bd0deb5f7f310b1022c3"}, + "ash": {:hex, :ash, "3.0.15", "1cea8ca799dc8281d09e316a189fddfb27a2a29a0b0e5af369aa83d6f731ca75", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "97abfed57f2bf29c3889c51f0dfa23b382a1361a9d70e7d82d7e59a2be6bdc73"}, "ash_sql": {:hex, :ash_sql, "0.2.5", "8b50c3178776263b912e1b60e161e2bcf08a907a38abf703edf8a8a0a51b3fe2", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0d5d8606738a17c4e8c0be4244623df721abee5072cee69d31c2711c36d0548f"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/load_test.exs b/test/load_test.exs index 200e1f29..4ab6d325 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Post, Record, TempEntity, User, StatefulPostFollower} + alias AshPostgres.Test.{Author, Comment, Post, Record, StatefulPostFollower, TempEntity, User} require Ash.Query diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 1a323202..804ac50b 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration creates unique_indexes using the `source` on the attributes of the identity on the resource assert file_contents =~ - ~S{create unique_index(:posts, ["t_w_s", "title"], name: "posts_thing_with_source_index")} + ~S{create unique_index(:posts, ["title", "t_w_s"], name: "posts_thing_with_source_index")} end end From c539e53f12eb1bded399a4f7cc89945480101974 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Jun 2024 16:37:56 -0400 Subject: [PATCH 0509/1215] chore: release version v2.0.10 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f226053..84b5becf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.10](https://github.com/ash-project/ash_postgres/compare/v2.0.9...v2.0.10) (2024-06-18) + + + + +### Bug Fixes: + +* update ash_sql to fix query generation issues + +* ensure that parens are always added to calculation generated SQL + +* properly get calculation sql + +### Improvements: + +* better type handling using new type inference + +* identities w/ calculations and where clauses in upserts + ## [v2.0.9](https://github.com/ash-project/ash_postgres/compare/v2.0.8...v2.0.9) (2024-06-13) diff --git a/mix.exs b/mix.exs index e53450e2..d219fc15 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.9" + @version "2.0.10" def project do [ From 94f7c2f58224ab4c6134cd4519deab9ad93b3835 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Jun 2024 16:38:19 -0400 Subject: [PATCH 0510/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 560cd842..864f1461 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.15", "1cea8ca799dc8281d09e316a189fddfb27a2a29a0b0e5af369aa83d6f731ca75", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "97abfed57f2bf29c3889c51f0dfa23b382a1361a9d70e7d82d7e59a2be6bdc73"}, - "ash_sql": {:hex, :ash_sql, "0.2.5", "8b50c3178776263b912e1b60e161e2bcf08a907a38abf703edf8a8a0a51b3fe2", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0d5d8606738a17c4e8c0be4244623df721abee5072cee69d31c2711c36d0548f"}, + "ash_sql": {:hex, :ash_sql, "0.2.6", "097a191b138af6bd5104ae0e166db5deb443fe3a4616b349fffe98120382765d", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "076abb07d7762537880a12699b756c7c86c1a4e98fcd0dc8c8059de93f7e9265"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, From 8bf1e2ede986fed0cd002740ac0c751ea974398d Mon Sep 17 00:00:00 2001 From: Emad Shaaban Date: Wed, 19 Jun 2024 21:22:21 +0300 Subject: [PATCH 0511/1215] fix: ensure index keys are atoms in generated migrations (#332) --------- Co-authored-by: Zach Daniel --- .../migration_generator.ex | 30 ++++++++++++++++++- .../20240618102809_migrate_resources30.exs | 2 +- test/migration_generator_test.exs | 24 +++++++-------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2d96b5d5..b586f6d6 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2884,7 +2884,7 @@ defmodule AshPostgres.MigrationGenerator do Enum.map(keys, fn key -> case Ash.Resource.Info.field(resource, key) do %Ash.Resource.Attribute{} = attribute -> - to_string(attribute.source || attribute.name) + attribute.source || attribute.name %Ash.Resource.Calculation{} -> sql = @@ -3000,6 +3000,17 @@ defmodule AshPostgres.MigrationGenerator do %{index | fields: fields} end) end) + |> Map.update!(:identities, fn identities -> + Enum.map(identities, fn identity -> + keys = + Enum.map(identity.keys, fn + value when is_binary(value) -> %{"type" => "string", "value" => value} + value when is_atom(value) -> %{"type" => "atom", "value" => value} + end) + + %{identity | keys: keys} + end) + end) |> Jason.encode!(pretty: true) end @@ -3251,6 +3262,23 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put_new(:all_tenants?, false) |> Map.put_new(:where, nil) |> Map.put_new(:nils_distinct?, true) + |> Map.update!( + :keys, + &Enum.map(&1, fn + %{type: "atom", value: value} -> + maybe_to_atom(value) + + %{type: "string", value: value} -> + value + + value -> + if String.contains?(value, "(") do + value + else + maybe_to_atom(value) + end + end) + ) end defp add_index_name(%{name: name} = index, table) do diff --git a/priv/test_repo/migrations/20240618102809_migrate_resources30.exs b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs index 5a4aa79b..f3cec44b 100644 --- a/priv/test_repo/migrations/20240618102809_migrate_resources30.exs +++ b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.TestRepo.Migrations.MigrateResources29 do +defmodule AshPostgres.TestRepo.Migrations.MigrateResources30 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 804ac50b..40c8ea3e 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -156,15 +156,15 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration creates unique_indexes based on the identities of the resource assert file_contents =~ - ~S{create unique_index(:posts, ["title"], name: "posts_title_index")} + ~S{create unique_index(:posts, [:title], name: "posts_title_index")} # the migration creates unique_indexes based on the identities of the resource assert file_contents =~ - ~S{create unique_index(:posts, ["title", "second_title"], name: "posts_thing_index")} + ~S{create unique_index(:posts, [:title, :second_title], name: "posts_thing_index")} # the migration creates unique_indexes using the `source` on the attributes of the identity on the resource assert file_contents =~ - ~S{create unique_index(:posts, ["title", "t_w_s"], name: "posts_thing_with_source_index")} + ~S{create unique_index(:posts, [:title, :t_w_s], name: "posts_thing_with_source_index")} end end @@ -241,7 +241,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file_contents =~ ~S{create index(:posts, ["id"]} assert file_contents =~ - ~S{create unique_index(:posts, ["second_title"], name: "posts_second_title_index", prefix: "example", nulls_distinct: false, where: "((second_title like '%foo%'))")} + ~S{create unique_index(:posts, [:second_title], name: "posts_second_title_index", prefix: "example", nulls_distinct: false, where: "((second_title like '%foo%'))")} # the migration adds the id, with its default assert file_contents =~ @@ -255,7 +255,7 @@ defmodule AshPostgres.MigrationGeneratorTest do # the migration creates unique_indexes based on the identities of the resource assert file_contents =~ - ~S{create unique_index(:posts, ["title"], name: "posts_title_index", prefix: "example")} + ~S{create unique_index(:posts, [:title], name: "posts_title_index", prefix: "example")} end end @@ -535,11 +535,11 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [file_before, _] = String.split( File.read!(file2), - ~S{create unique_index(:posts, ["title"], name: "posts_title_index", where: "(title != 'fred' and title != 'george')")} + ~S{create unique_index(:posts, [:title], name: "posts_title_index", where: "(title != 'fred' and title != 'george')")} ) assert file_before =~ - ~S{drop_if_exists unique_index(:posts, ["title"], name: "posts_title_index")} + ~S{drop_if_exists unique_index(:posts, [:title], name: "posts_title_index")} end test "when adding a field, it adds the field" do @@ -753,18 +753,18 @@ defmodule AshPostgres.MigrationGeneratorTest do file1_content = File.read!(file1) assert file1_content =~ - "create unique_index(:posts, [\"title\"], name: \"posts_title_index\")" + "create unique_index(:posts, [:title], name: \"posts_title_index\")" file2_content = File.read!(file2) assert file2_content =~ - "drop_if_exists unique_index(:posts, [\"title\"], name: \"posts_title_index\")" + "drop_if_exists unique_index(:posts, [:title], name: \"posts_title_index\")" assert file2_content =~ - "create unique_index(:posts, [\"name\"], name: \"posts_unique_name_index\")" + "create unique_index(:posts, [:name], name: \"posts_unique_name_index\")" assert file2_content =~ - "create unique_index(:posts, [\"title\"], name: \"posts_unique_title_index\")" + "create unique_index(:posts, [:title], name: \"posts_unique_title_index\")" end test "when an attribute exists only on some of the resources that use the same table, it isn't marked as null: false" do @@ -1241,7 +1241,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") assert File.read!(file) =~ - ~S{create unique_index(:users, ["name"], name: "users_unique_name_index")} + ~S{create unique_index(:users, [:name], name: "users_unique_name_index")} end test "when modified, the foreign key is dropped before modification" do From 10f106490ce7e061148f1444026f358df2e6dc78 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 19 Jun 2024 19:30:36 -0400 Subject: [PATCH 0512/1215] fix: rework expression type detection --- lib/sql_implementation.ex | 301 +++++++++++++++++++------------------- 1 file changed, 154 insertions(+), 147 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index d037d2d9..a827fe51 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -232,181 +232,188 @@ defmodule AshPostgres.SqlImplementation do |> then(fn types -> Enum.concat(Map.keys(Ash.Query.Operator.operator_overloads(name) || %{}), types) end) - |> Enum.flat_map(fn types -> - case types do - :same -> - types = - for _ <- values do - :same - end + |> Enum.reject(&(&1 == :any)) + |> Enum.filter(fn typeset -> + typeset == :same || + length(typeset) == length(values) + end) + |> Enum.find_value(Enum.map(values, fn _ -> nil end), fn typeset -> + types_and_values = + if typeset == :same do + Enum.map(values, &{:same, &1}) + else + Enum.zip(typeset, values) + end - [closest_fitting_type(types, values)] + types_and_values + |> Enum.with_index() + |> Enum.reduce_while(%{must_adopt_basis: [], basis: nil, types: []}, fn + {{vague_type, value}, index}, acc when vague_type in [:any, :same] -> + case determine_type(value) do + {:ok, {type, constraints}} -> + case acc[:basis] do + nil -> + if vague_type == :any do + acc = Map.update!(acc, :types, &[nil | &1]) + {:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])} + else + acc = Map.update!(acc, :types, &[{type, constraints} | &1]) + {:cont, Map.put(acc, :basis, {type, constraints})} + end - :any -> - [] + {^type, matched_constraints} -> + {:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])} - types -> - [types] - end - end) - # this doesn't seem right to me - |> Enum.filter(fn types -> - Enum.all?(types, &(vagueness(&1) == 0)) - end) - |> case do - [types] -> - types - - types -> - Enum.find_value(types, Enum.map(values, fn _ -> nil end), fn types -> - if length(types) == length(values) do - types - |> Enum.zip(values) - |> Enum.reduce_while([], fn {type, value}, vals -> - type = Ash.Type.get_type(type) - # this means its a known type - if Ash.Type.ash_type?(type) do - {type, constraints} = - case type do - {type, constraints} -> {type, constraints} - type -> {type, []} + _ -> + {:halt, :error} + end + + :error -> + acc = Map.update!(acc, :types, &[nil | &1]) + {:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])} + end + + {{{:array, vague_type}, value}, index}, acc when vague_type in [:any, :same] -> + case determine_type(value) do + {:ok, {{:array, type}, constraints}} -> + case acc[:basis] do + nil -> + if vague_type == :any do + acc = Map.update!(acc, :types, &[nil | &1]) + + {:cont, + Map.update!( + acc, + :must_adopt_basis, + &[ + {index, + fn {type, constraints} -> {{:array, type}, items: constraints} end} + | &1 + ] + )} + else + acc = Map.update!(acc, :types, &[{:array, {type, constraints}} | &1]) + {:cont, Map.put(acc, :basis, {type, constraints})} end - case value do - %Ash.Query.Function.Type{arguments: [_, ^type | _]} -> - {:cont, vals ++ [:any]} - - %Ash.Query.Ref{attribute: %{type: ^type}} -> - {:cont, vals ++ [:any]} - - _ -> - if Ash.Type.matches_type?(type, value, constraints) do - {:cont, vals ++ [parameterized_type(type, constraints)]} - else - {:halt, nil} - end - end - else - {:halt, nil} + {^type, matched_constraints} -> + {:cont, Map.update!(acc, :types, &[{:array, {type, matched_constraints}} | &1])} + + _ -> + {:halt, :error} end - end) + + _ -> + acc = Map.update!(acc, :types, &[nil | &1]) + + {:cont, + Map.update!( + acc, + :must_adopt_basis, + &[ + {index, fn {type, constraints} -> {{:array, type}, items: constraints} end} + | &1 + ] + )} end - end) - end - end - defp closest_fitting_type(types, values) do - types_with_values = Enum.zip(types, values) + {{{type, constraints}, value}, _index}, acc -> + cond do + !Ash.Expr.expr?(value) && !Ash.Type.matches_type?(type, value, constraints) -> + {:halt, :error} - types_with_values - |> fill_in_known_types() - |> clarify_types() - end + Ash.Expr.expr?(value) -> + case determine_type(value) do + {:ok, {^type, matched_constraints}} -> + {:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])} - defp clarify_types(types) do - basis = - types - |> Enum.map(&elem(&1, 0)) - |> Enum.min_by(&vagueness(&1)) - - Enum.map(types, fn {type, _value} -> - replace_same(type, basis) - end) - end + _ -> + {:halt, :error} + end - defp replace_same({:in, type}, basis) do - {:in, replace_same(type, basis)} - end + true -> + {:cont, Map.update!(acc, :types, &[{type, constraints} | &1])} + end - defp replace_same(:same, :same) do - :any - end + {{type, value}, _index}, acc -> + cond do + !Ash.Expr.expr?(value) && !Ash.Type.matches_type?(type, value, []) -> + {:halt, :error} - defp replace_same(:same, {:in, :same}) do - {:in, :any} - end + Ash.Expr.expr?(value) -> + case determine_type(value) do + {:ok, {^type, matched_constraints}} -> + {:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])} - defp replace_same(:same, basis) do - basis - end + _ -> + {:halt, :error} + end - defp replace_same(other, _basis) do - other - end + true -> + {:cont, Map.update!(acc, :types, &[{type, []} | &1])} + end + end) + |> case do + :error -> + nil - defp fill_in_known_types(types) do - Enum.map(types, &fill_in_known_type/1) - end + %{basis: nil, must_adopt_basis: [], types: types} -> + types + |> Enum.reverse() + |> Enum.map(fn {type, constraints} -> + parameterized_type(type, constraints) + end) - defp fill_in_known_type( - {{:array, type}, - %Ash.Query.Function.Type{arguments: [inner, {:array, type}, constraints]} = func} - ) do - {:in, - fill_in_known_type({type, %{func | arguments: [inner, type, constraints[:items] || []]}})} - end + %{basis: nil, must_adopt_basis: _} -> + nil - defp fill_in_known_type( - {{:array, type}, - %Ash.Query.Ref{attribute: %{type: {:array, type}, constraints: constraints} = attribute} = - ref} - ) do - {:in, - fill_in_known_type( - {type, - %{ref | attribute: %{attribute | type: type, constraints: constraints[:items] || []}}} - )} + %{basis: basis, must_adopt_basis: basis_adopters, types: types} -> + basis_adopters + |> Enum.reduce( + Enum.reverse(types), + fn {index, function_of_basis}, types -> + List.replace_at(types, index, function_of_basis.(basis)) + end + ) + |> Enum.map(fn {type, constraints} -> + parameterized_type(type, constraints) + end) + end + end) end - defp fill_in_known_type( - {vague_type, %Ash.Query.Function.Type{arguments: [_, type, constraints]}} = func - ) - when vague_type in [:any, :same] do - if Ash.Type.ash_type?(type) do - type = type |> parameterized_type(constraints) |> array_to_in() - - {type || :any, func} - else - type = - if is_atom(type) && :erlang.function_exported(type, :type, 1) do - Ecto.ParameterizedType.init(type, []) |> array_to_in() + defp determine_type(value) do + case value do + %Ash.Query.Function.Type{arguments: [_, type, constraints]} -> + if Ash.Type.ash_type?(type) do + {:ok, {type, constraints}} else - type |> array_to_in() + :error end - {type, func} - end - end + %Ash.Query.Function.Type{arguments: [_, type]} -> + if Ash.Type.ash_type?(type) do + {:ok, {type, []}} + else + :error + end - defp fill_in_known_type( - {vague_type, %Ash.Query.Ref{attribute: %{type: type, constraints: constraints}}} = ref - ) - when vague_type in [:any, :same] do - if Ash.Type.ash_type?(type) do - type = type |> parameterized_type(constraints) |> array_to_in() + %Ash.Query.Ref{attribute: %{type: type, constraints: constraints}} -> + if Ash.Type.ash_type?(type) do + {:ok, {type, constraints}} + else + :error + end - {type || :any, ref} - else - type = - if is_atom(type) && :erlang.function_exported(type, :type, 1) do - Ecto.ParameterizedType.init(type, []) |> array_to_in() + %Ash.Query.Ref{attribute: %{type: type}} -> + if Ash.Type.ash_type?(type) do + {:ok, {type, []}} else - type |> array_to_in() + :error end - {type, ref} + _ -> + :error end end - - defp fill_in_known_type({type, value}), - do: {array_to_in(type), value} - - defp array_to_in({:array, v}), do: {:in, array_to_in(v)} - - defp array_to_in(v), do: v - - defp vagueness({:in, type}), do: vagueness(type) - defp vagueness(:same), do: 2 - defp vagueness(:any), do: 1 - defp vagueness(_), do: 0 end From 95a3ff3c00ce103a61a4e22d2a5d98f7e07772df Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 19 Jun 2024 19:49:33 -0400 Subject: [PATCH 0513/1215] fix: store fallback_basis for type detection --- lib/sql_implementation.ex | 42 +++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index a827fe51..61ea99c0 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -247,18 +247,22 @@ defmodule AshPostgres.SqlImplementation do types_and_values |> Enum.with_index() - |> Enum.reduce_while(%{must_adopt_basis: [], basis: nil, types: []}, fn + |> Enum.reduce_while(%{must_adopt_basis: [], basis: nil, types: [], fallback_basis: nil}, fn {{vague_type, value}, index}, acc when vague_type in [:any, :same] -> case determine_type(value) do {:ok, {type, constraints}} -> case acc[:basis] do nil -> if vague_type == :any do - acc = Map.update!(acc, :types, &[nil | &1]) - {:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])} - else acc = Map.update!(acc, :types, &[{type, constraints} | &1]) {:cont, Map.put(acc, :basis, {type, constraints})} + else + acc = + acc + |> Map.update!(:types, &[nil | &1]) + |> Map.put(:fallback_basis, {type, constraints}) + + {:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])} end {^type, matched_constraints} -> @@ -279,7 +283,13 @@ defmodule AshPostgres.SqlImplementation do case acc[:basis] do nil -> if vague_type == :any do - acc = Map.update!(acc, :types, &[nil | &1]) + acc = Map.update!(acc, :types, &[{:array, {type, constraints}} | &1]) + {:cont, Map.put(acc, :basis, {type, constraints})} + else + acc = + acc + |> Map.update!(:types, &[nil | &1]) + |> Map.put(:fallback_basis, {type, constraints}) {:cont, Map.update!( @@ -291,9 +301,6 @@ defmodule AshPostgres.SqlImplementation do | &1 ] )} - else - acc = Map.update!(acc, :types, &[{:array, {type, constraints}} | &1]) - {:cont, Map.put(acc, :basis, {type, constraints})} end {^type, matched_constraints} -> @@ -319,7 +326,7 @@ defmodule AshPostgres.SqlImplementation do {{{type, constraints}, value}, _index}, acc -> cond do - !Ash.Expr.expr?(value) && !Ash.Type.matches_type?(type, value, constraints) -> + !Ash.Expr.expr?(value) && !matches_type?(type, value, constraints) -> {:halt, :error} Ash.Expr.expr?(value) -> @@ -337,7 +344,7 @@ defmodule AshPostgres.SqlImplementation do {{type, value}, _index}, acc -> cond do - !Ash.Expr.expr?(value) && !Ash.Type.matches_type?(type, value, []) -> + !Ash.Expr.expr?(value) && !matches_type?(type, value, []) -> {:halt, :error} Ash.Expr.expr?(value) -> @@ -353,6 +360,13 @@ defmodule AshPostgres.SqlImplementation do {:cont, Map.update!(acc, :types, &[{type, []} | &1])} end end) + |> then(fn + %{basis: nil, fallback_basis: fallback_basis} = data when not is_nil(fallback_basis) -> + %{data | basis: fallback_basis} + + data -> + data + end) |> case do :error -> nil @@ -416,4 +430,12 @@ defmodule AshPostgres.SqlImplementation do :error end end + + defp matches_type?({:array, type}, %MapSet{} = value, constraints) do + Enum.all?(value, &matches_type?(&1, type, constraints[:items])) + end + + defp matches_type?(type, value, constraints) do + Ash.Type.matches_type?(type, value, constraints) + end end From 51c5c3c8469b3f936e6a5959ae038ede95f4dbd8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 19 Jun 2024 20:03:27 -0400 Subject: [PATCH 0514/1215] chore: release version v2.0.11 --- CHANGELOG.md | 41 +++++++++++++++++++---------------------- mix.exs | 2 +- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b5becf..78abe7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,59 +5,56 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline -## [v2.0.10](https://github.com/ash-project/ash_postgres/compare/v2.0.9...v2.0.10) (2024-06-18) +## [v2.0.11](https://github.com/ash-project/ash_postgres/compare/v2.0.10...v2.0.11) (2024-06-19) + +### Bug Fixes: +- [AshPostgres.DataLayer] rework expression type detection +- [migration generator] ensure index keys are atoms in generated migrations (#332) +## [v2.0.10](https://github.com/ash-project/ash_postgres/compare/v2.0.9...v2.0.10) (2024-06-18) ### Bug Fixes: -* update ash_sql to fix query generation issues +- [AshPostgres.DataLayer] update ash_sql to fix query generation issues -* ensure that parens are always added to calculation generated SQL +- [migration generator] ensure that parens are always added to calculation generated SQL -* properly get calculation sql +- [migration generator] properly get calculation sql ### Improvements: -* better type handling using new type inference +- [AshPostgres.DataLayer] better type handling using new type inference -* identities w/ calculations and where clauses in upserts +- [identities] identities w/ calculations and where clauses in upserts ## [v2.0.9](https://github.com/ash-project/ash_postgres/compare/v2.0.8...v2.0.9) (2024-06-13) - - - ### Features: -* autogenerate index in references (#321) - -* autogenerate index in references +- [migration generator] autogenerate index in references (#321) ### Bug Fixes: -* fix invalid select on sorting by some calculations +- [AshPostgres.DataLayer] fix invalid select on sorting by some calculations -* fix error message displaying in identity verifier +- [AshPostgres.DataLayer] fix error message displaying in identity verifier -* ensure that context multitenancy is properly applied to lateral many-to-many joins +- [lateral joining] ensure that context multitenancy is properly applied to lateral many-to-many joins -* don't assume old snapshots have `index?` key for attributes +- [migration generator] don't assume old snapshots have `index?` key for attributes -* `list_tenants` -> `all_tenants` +- [ash.rollback] `list_tenants` -> `all_tenants` -* when checking for roll back-able migrations, only check `Path.basename` +- [ash.rollback] when checking for roll back-able migrations, only check `Path.basename` ### Improvements: -* don't sort identity keys. +- [migration generator] don't sort identity keys. ## [v2.0.8](https://github.com/ash-project/ash_postgres/compare/v2.0.7...v2.0.8) (2024-06-06) - - - ## [v2.0.7](https://github.com/ash-project/ash_postgres/compare/v2.0.6...v2.0.7) (2024-06-06) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index d219fc15..2264c23a 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.10" + @version "2.0.11" def project do [ From b53ef4f2814243b250105a5b05a8d73b0a982b9e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 20 Jun 2024 12:02:47 -0400 Subject: [PATCH 0515/1215] chore: update docs description --- lib/mix/tasks/ash_postgres.migrate.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 75fa6f46..9f7cf4c5 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -42,10 +42,9 @@ defmodule Mix.Tasks.AshPostgres.Migrate do specific version number, supply `--to version_number`. To migrate a specific number of times, use `--step n`. - This is only really useful if your domains only use a single repo. If you have multiple repos and you want to run a single migration and/or - migrate/roll them back to different points, you will need to use the - ecto specific task, `mix ecto.migrate` and provide your repo name. + migrate them to different points, you will need to use the ecto specific task, + `mix ecto.migrate` and provide your repo name. If a repository has not yet been started, one will be started outside your application supervision tree and shutdown afterwards. From bd8657e593bd1fb0d7518be9762fda84c6083201 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 20 Jun 2024 17:32:04 -0400 Subject: [PATCH 0516/1215] fix: only add references indexes if they've changed --- lib/migration_generator/migration_generator.ex | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index b586f6d6..9ea40b1b 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1759,7 +1759,16 @@ defmodule AshPostgres.MigrationGenerator do reference_indexes_to_add = Enum.filter(snapshot.attributes, fn attribute -> - if attribute.references, do: attribute.references[:index?] + case Enum.find(old_snapshot.attributes, fn old_attribute -> + old_attribute.source == attribute.source + end) do + nil -> + attribute.references && attribute.references[:index?] + + old_attribute -> + (old_attribute.references && old_attribute.references[:index?]) != + (attribute.references && attribute.references[:index?]) + end end) |> Enum.map(fn attribute -> %Operation.AddReferenceIndex{ From 9b82696dbf8b3b52dd53bb05958b19170a0134ac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 20 Jun 2024 17:43:05 -0400 Subject: [PATCH 0517/1215] chore: release version v2.0.12 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78abe7de..e05cd166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.0.12](https://github.com/ash-project/ash_postgres/compare/v2.0.11...v2.0.12) (2024-06-20) + + + + +### Bug Fixes: + +* only add references indexes if they've changed + ## [v2.0.11](https://github.com/ash-project/ash_postgres/compare/v2.0.10...v2.0.11) (2024-06-19) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 2264c23a..d09bb7a1 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.11" + @version "2.0.12" def project do [ From 46279527bfb6c2572ec1e0915992cff8c81304fc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 20 Jun 2024 17:43:20 -0400 Subject: [PATCH 0518/1215] chore: update changelog --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e05cd166..add91b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,9 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.0.12](https://github.com/ash-project/ash_postgres/compare/v2.0.11...v2.0.12) (2024-06-20) - - - ### Bug Fixes: -* only add references indexes if they've changed +- [migration generator] only add references indexes if they've changed ## [v2.0.11](https://github.com/ash-project/ash_postgres/compare/v2.0.10...v2.0.11) (2024-06-19) From 6b1f6bae2a3c0ddd57e9ef88d951156046695eb2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Jun 2024 19:49:47 -0400 Subject: [PATCH 0519/1215] improvement: add `mix ash_postgres.install` (mix igniter.install ash_postgres) --- .tool-versions | 4 +- lib/data_layer.ex | 20 ++ .../migration_generator.ex | 9 + lib/mix/helpers.ex | 77 +++-- .../tasks/ash_postgres.generate_migrations.ex | 9 +- lib/mix/tasks/ash_postgres.install.ex | 287 ++++++++++++++++++ mix.lock | 4 +- priv/test_no_sandbox_repo/migrations/.gitkeep | 0 test/migration_generator_test.exs | 2 +- 9 files changed, 375 insertions(+), 37 deletions(-) create mode 100644 lib/mix/tasks/ash_postgres.install.ex create mode 100644 priv/test_no_sandbox_repo/migrations/.gitkeep diff --git a/.tool-versions b/.tool-versions index 9a50e189..61c2916b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 26.2.2 -elixir 1.16.2 +erlang 27.0 +elixir 1.17.0 diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 15be6469..feda08dc 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -401,6 +401,8 @@ defmodule AshPostgres.DataLayer do A postgres data layer that leverages Ecto's postgres capabilities. """ + require Igniter.Code.Common + use Spark.Dsl.Extension, sections: @sections, verifiers: [ @@ -2919,6 +2921,24 @@ defmodule AshPostgres.DataLayer do end end + if Code.ensure_loaded?(Igniter) do + def install(igniter, module, Ash.Resource, path, _argv) do + table_name = + module + |> Module.split() + |> List.last() + |> Macro.underscore() + + repo = Igniter.Code.Module.module_name("Repo") + + igniter + |> Spark.Igniter.set_option(Ash.Resource, path, [:postgres, :table], table_name) + |> Spark.Igniter.set_option(Ash.Resource, path, [:postgres, :repo], repo) + end + + def install(igniter, _, _, _), do: igniter + end + @impl true def rollback(resource, term) do AshPostgres.DataLayer.Info.repo(resource, :mutate).rollback(term) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 9ea40b1b..85f4f7c0 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -45,6 +45,7 @@ defmodule AshPostgres.MigrationGenerator do repos = (snapshots ++ tenant_snapshots) |> Enum.map(& &1.repo) + |> Enum.concat(find_repos()) |> Enum.uniq() Mix.shell().info("\nExtension Migrations: ") @@ -55,6 +56,14 @@ defmodule AshPostgres.MigrationGenerator do create_migrations(snapshots, opts, false) end + defp find_repos do + Mix.Project.config()[:app] + |> Application.get_env(:ecto_repos, []) + |> Enum.filter(fn repo -> + Spark.implements_behaviour?(repo, AshPostgres.Repo) + end) + end + @doc """ A work in progress utility for getting snapshots. diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 685b7e3d..dc0918a3 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -1,6 +1,6 @@ defmodule AshPostgres.Mix.Helpers do @moduledoc false - def domains!(opts, args) do + def domains!(opts, args, error_on_no_domains? \\ true) do apps = if apps_paths = Mix.Project.apps_paths() do apps_paths |> Map.keys() |> Enum.sort() @@ -30,7 +30,11 @@ defmodule AshPostgres.Mix.Helpers do |> Enum.map(&ensure_compiled(&1, args)) |> case do [] -> - raise "must supply the --domains argument, or set `config :my_app, ash_domains: [...]` in config" + if error_on_no_domains? do + raise "must supply the --domains argument, or set `config :my_app, ash_domains: [...]` in config" + else + [] + end domains -> domains @@ -38,43 +42,54 @@ defmodule AshPostgres.Mix.Helpers do end def repos!(opts, args) do - domains = domains!(opts, args) + if opts[:domains] && opts[:domains] != "" do + domains = domains!(opts, args) - resources = - domains - |> Enum.flat_map(&Ash.Domain.Info.resources/1) - |> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer)) + resources = + domains + |> Enum.flat_map(&Ash.Domain.Info.resources/1) + |> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer)) + |> case do + [] -> + raise """ + No resources with `data_layer: AshPostgres.DataLayer` found in the domains #{Enum.map_join(domains, ",", &inspect/1)}. + + Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`. + """ + + resources -> + resources + end + + resources + |> Enum.flat_map( + &[ + AshPostgres.DataLayer.Info.repo(&1, :read), + AshPostgres.DataLayer.Info.repo(&1, :mutate) + ] + ) + |> Enum.uniq() |> case do [] -> raise """ - No resources with `data_layer: AshPostgres.DataLayer` found in the domains #{Enum.map_join(domains, ",", &inspect/1)}. - - Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`. - """ + No repos could be found configured on the resources in the domains: #{Enum.map_join(domains, ",", &inspect/1)} - resources -> - resources - end + At least one resource must have a repo configured. - resources - |> Enum.flat_map( - &[AshPostgres.DataLayer.Info.repo(&1, :read), AshPostgres.DataLayer.Info.repo(&1, :mutate)] - ) - |> Enum.uniq() - |> case do - [] -> - raise """ - No repos could be found configured on the resources in the domains: #{Enum.map_join(domains, ",", &inspect/1)} + The following resources were found with `data_layer: AshPostgres.DataLayer`: - At least one resource must have a repo configured. - - The following resources were found with `data_layer: AshPostgres.DataLayer`: - - #{Enum.map_join(resources, "\n", &"* #{inspect(&1)}")} - """ + #{Enum.map_join(resources, "\n", &"* #{inspect(&1)}")} + """ - repos -> - repos + repos -> + repos + end + else + Mix.Project.config()[:app] + |> Application.get_env(:ecto_repos, []) + |> Enum.filter(fn repo -> + Spark.implements_behaviour?(repo, AshPostgres.Repo) + end) end end diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 5348d27a..2c31d0f7 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -98,7 +98,14 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do ] ) - domains = AshPostgres.Mix.Helpers.domains!(opts, args) + domains = AshPostgres.Mix.Helpers.domains!(opts, args, false) + + if Enum.empty?(domains) do + IO.warn(""" + No domains found, so no resource-related migrations will be generated. + Pass the `--domains` option or configure `config :your_app, ash_domains: [...]` + """) + end opts = opts diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex new file mode 100644 index 00000000..61991661 --- /dev/null +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -0,0 +1,287 @@ +defmodule Mix.Tasks.AshPostgres.Install do + @moduledoc "Installs AshPostgres. Should be run with `mix igniter.install ash_postgres`" + @shortdoc @moduledoc + require Igniter.Code.Common + use Igniter.Mix.Task + + def igniter(igniter, _argv) do + repo = Igniter.Code.Module.module_name("Repo") + otp_app = Igniter.Project.Application.app_name() + + igniter + |> Igniter.Project.Formatter.import_dep(:ash_postgres) + |> setup_repo_module(otp_app, repo) + |> configure_config(otp_app, repo) + |> configure_dev(otp_app, repo) + |> configure_test(otp_app, repo) + |> configure_runtime(repo, otp_app) + |> Igniter.Project.Application.add_new_child(repo) + |> Igniter.add_task("ash.codegen", ["install_ash_postgres"]) + end + + defp configure_config(igniter, otp_app, repo) do + Igniter.Project.Config.configure( + igniter, + "config.exs", + otp_app, + [:ecto_repos], + [repo], + updater: fn zipper -> + Igniter.Code.List.prepend_new_to_list( + zipper, + repo + ) + end + ) + end + + defp configure_runtime(igniter, otp_app, repo) do + default_runtime = """ + import Config + + if config_env() == :prod do + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, Helpdesk.Repo, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + end + """ + + igniter + |> Igniter.create_or_update_elixir_file("config/runtime.exs", default_runtime, fn zipper -> + if Igniter.Project.Config.configures?(zipper, [repo, :url], otp_app) do + zipper + else + patterns = [ + """ + if config_env() == :prod do + __cursor__() + end + """, + """ + if :prod == config_env() do + __cursor__() + end + """ + ] + + zipper + |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) + |> case do + {:ok, zipper} -> + case Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :=, + 2, + fn call -> + Igniter.Code.Function.argument_matches_predicate?( + call, + 0, + &match?({:database_url, _, Elixir}, &1) + ) + end + ) do + {:ok, zipper} -> + zipper + |> Igniter.Project.Config.modify_configuration_code( + [repo, :url], + otp_app, + {:database_url, [], Elixir} + ) + |> Igniter.Project.Config.modify_configuration_code( + [repo, :pool_size], + otp_app, + quote do + String.to_integer(System.get_env("POOL_SIZE") || "10") + end + ) + |> then(&{:ok, &1}) + + :error -> + Igniter.Code.Common.add_code(zipper, """ + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, Helpdesk.Repo, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + end + + :error -> + Igniter.Code.Common.add_code(zipper, """ + if config_env() == :prod do + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, Helpdesk.Repo, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + end + """) + end + end + end) + end + + defp configure_dev(igniter, otp_app, repo) do + igniter + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :username], "postgres") + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :password], "postgres") + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :hostname], "localhost") + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :database], + "#{otp_app}_dev" + ) + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :show_sensitive_data_on_connection_error], + true + ) + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :pool_size], 10) + end + + defp configure_test(igniter, otp_app, repo) do + database = + {:<<>>, [], + [ + "#{otp_app}_test", + {:"::", [], + [ + {{:., [], [Kernel, :to_string]}, [from_interpolation: true], + [ + {{:., [], [{:__aliases__, [alias: false], [:System]}, :get_env]}, [], + ["MIX_TEST_PARTITION"]} + ]}, + {:binary, [], Elixir} + ]} + ]} + |> Sourceror.to_string() + |> Sourceror.parse_string!() + + igniter + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :username], "postgres") + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :password], "postgres") + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :hostname], "localhost") + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :database], + {:code, database} + ) + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :pool], + Ecto.Adapters.SQL.Sandbox + ) + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) + end + + defp setup_repo_module(igniter, otp_app, repo) do + path = Igniter.Code.Module.proper_location(repo) + + default_repo_contents = + """ + defmodule #{inspect(repo)} do + use AshPostgres.Repo, otp_app: #{inspect(otp_app)} + + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + end + """ + + igniter + |> Igniter.create_or_update_elixir_file(path, default_repo_contents, fn zipper -> + zipper + |> set_otp_app(otp_app) + |> Sourceror.Zipper.top() + |> use_ash_postgres_instead_of_ecto() + |> Sourceror.Zipper.top() + |> add_installed_extensions_function() + |> Sourceror.Zipper.top() + |> remove_adapter_option() + end) + end + + defp use_ash_postgres_instead_of_ecto(zipper) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Ecto.Repo), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Ecto.Repo), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 0, fn zipper -> + {:ok, Igniter.Code.Common.replace_code(zipper, AshPostgres.Repo)} + end) do + zipper + else + _ -> + zipper + end + end + + defp remove_adapter_option(zipper) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 1, fn values_zipper -> + Igniter.Code.Keyword.remove_keyword_key(values_zipper, :adapter) + end) do + zipper + else + _ -> + zipper + end + end + + defp set_otp_app(zipper, otp_app) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 0, fn zipper -> + {:ok, Igniter.Code.Common.replace_code(zipper, AshPostgres.Repo)} + end), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 1, fn values_zipper -> + values_zipper + |> Igniter.Code.Keyword.set_keyword_key(:otp_app, otp_app, fn x -> {:ok, x} end) + end) do + zipper + else + _ -> + zipper + end + end + + defp add_installed_extensions_function(zipper) do + case Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo) do + {:ok, zipper} -> + Igniter.Code.Common.add_code(zipper, """ + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """) + + _ -> + zipper + end + end +end diff --git a/mix.lock b/mix.lock index 864f1461..4f0d9ac9 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, - "igniter": {:hex, :igniter, "0.1.7", "dd5674a6ec2ec1728a690d4915a5a430ba213b45cad86f5710cf9076e6274d0e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.3", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.1 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "08d95b91fee280be305887b128dcf10a4ff55a8ab72a452489b1529e8644a8c8"}, + "igniter": {:hex, :igniter, "0.2.1", "a11026b484eb6cd5fa13a84fc14f770411e38ba7824a3bb45142fba02dc1a9a3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.3", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3521fdf18baa6e6045a6765f1725ae24fac0d9deb435b484f762066a8f534d01"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -43,7 +43,7 @@ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"}, "spark": {:hex, :spark, "2.2.3", "d41c45f0d7e8289499bf104173543be41ee1d8d213cb7503e9328b30751f2f13", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "401dec157c8b88634c27f241f4e374476081c577f51d2b08512f2c681c25a852"}, - "spitfire": {:hex, :spitfire, "0.1.2", "49b85d59c170d671e7e49649f62f6fe0771743a61bc42bd7a203f98f322d99e2", [:mix], [], "hexpm", "21f04cf02df601d75e1551e393ee5c927e3986fbb7598f3e59f71d4ca544fd9b"}, + "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, diff --git a/priv/test_no_sandbox_repo/migrations/.gitkeep b/priv/test_no_sandbox_repo/migrations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 40c8ea3e..263c826d 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1409,7 +1409,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") assert File.read!(file) =~ - ~S[references(:users, column: :secondary_id, with: [org_id: :org_id\], match: :full, name: "user_things1_user_id_fkey", type: :uuid, prefix: "public")] + ~S{references(:users, column: :secondary_id, with: [org_id: :org_id], match: :full, name: "user_things1_user_id_fkey", type: :uuid, prefix: "public")} assert File.read!(file) =~ ~S[references(:users, column: :id, name: "user_things2_user_id_fkey", type: :uuid, prefix: "public")] From 6e60f1aea3d5fd5413222a22f9136e74fa3fe3ea Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 24 Jun 2024 08:20:09 -0400 Subject: [PATCH 0520/1215] ci: dependabot weekly --- .github/dependabot.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6977f1cb..2f0ee6ff 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,12 @@ version: 2 updates: -- package-ecosystem: mix - directory: "/" - schedule: - interval: "daily" + - package-ecosystem: mix + directory: "/" + schedule: + interval: weekly + day: thursday + groups: + production-dependencies: + dependency-type: production + dev-dependencies: + dependency-type: development From 804a32ef28850fbb28a2ff37d1f7cd8ca98ab2e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 08:20:37 -0400 Subject: [PATCH 0521/1215] chore(deps): bump ash from 3.0.15 to 3.0.16 (#334) Bumps [ash](https://github.com/ash-project/ash) from 3.0.15 to 3.0.16. - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.0.15...v3.0.16) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 4f0d9ac9..365c80d3 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.15", "1cea8ca799dc8281d09e316a189fddfb27a2a29a0b0e5af369aa83d6f731ca75", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "97abfed57f2bf29c3889c51f0dfa23b382a1361a9d70e7d82d7e59a2be6bdc73"}, + "ash": {:hex, :ash, "3.0.16", "8eaebd5a9f3ee404937ac811a240799613b0619026e097436132d60eaf18ed16", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36c0d7653f7fb1d13cc03e1cc7ea7f6b9aadd278b9c9375ff5f0636ed0d7a785"}, "ash_sql": {:hex, :ash_sql, "0.2.6", "097a191b138af6bd5104ae0e166db5deb443fe3a4616b349fffe98120382765d", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "076abb07d7762537880a12699b756c7c86c1a4e98fcd0dc8c8059de93f7e9265"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, - "igniter": {:hex, :igniter, "0.2.1", "a11026b484eb6cd5fa13a84fc14f770411e38ba7824a3bb45142fba02dc1a9a3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.3", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3521fdf18baa6e6045a6765f1725ae24fac0d9deb435b484f762066a8f534d01"}, + "igniter": {:hex, :igniter, "0.2.3", "932295ef076390ee655dbf761f6cdcf3dc5c993439ac3c950de6d823ef45c4d1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.3", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "0e9702d58aa592594e5aad5ca28a50e7bd01d536152d0a64ea4bd3684e58aea1"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -42,7 +42,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"}, - "spark": {:hex, :spark, "2.2.3", "d41c45f0d7e8289499bf104173543be41ee1d8d213cb7503e9328b30751f2f13", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "401dec157c8b88634c27f241f4e374476081c577f51d2b08512f2c681c25a852"}, + "spark": {:hex, :spark, "2.2.4", "077363750eec4d80ffd4b20075676d17fce8bf82af1aa6aa51d2a539685b8d83", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "fd92bdd4508852bcd445c463d0536d0f130ed828d90401d17a061bcdeca4372a"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From e520ddfc35a98d88146edf99ee23454e389c19d8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 24 Jun 2024 10:19:58 -0400 Subject: [PATCH 0522/1215] chore: get build passing fix: properly delete args passed from migrate to ecto --- lib/mix/tasks/ash_postgres.migrate.ex | 4 ++-- test/support/test_no_sandbox_repo.ex | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 9f7cf4c5..fd74ff65 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -109,8 +109,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do |> AshPostgres.Mix.Helpers.delete_arg("--domains") |> AshPostgres.Mix.Helpers.delete_arg("--migrations-path") |> AshPostgres.Mix.Helpers.delete_flag("--tenants") - |> AshPostgres.Mix.Helpers.delete_flag("--only-tenants") - |> AshPostgres.Mix.Helpers.delete_flag("--except-tenants") + |> AshPostgres.Mix.Helpers.delete_arg("--only-tenants") + |> AshPostgres.Mix.Helpers.delete_arg("--except-tenants") Mix.Task.reenable("ecto.migrate") diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index 567d35db..f1222459 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -13,10 +13,6 @@ defmodule AshPostgres.TestNoSandboxRepo do end def all_tenants do - Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) - - AshPostgres.MultitenancyTest.Org - |> Ash.read!() - |> Enum.map(&"org_#{&1.id}") + [] end end From c31c8685b9b80ecf168e3b5d6ae9fd9f47ca68a9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Jun 2024 08:25:17 -0400 Subject: [PATCH 0523/1215] docs: remove unused code from docs --- lib/verifiers/validate_identity_index_names.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index 7c046869..f09b95e9 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -46,7 +46,7 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do message: """ Identity #{identity.name} has a name that is too long. Names must be 63 characters or less. - Please configure an index name for this identity in the `identity_index_names` configuration. For example:application + Please configure an index name for this identity in the `identity_index_names` configuration. For example: postgres do identity_index_names #{inspect(identity.name)}: "a_shorter_name" From 278b2a31a559b5700e936ddacad4259adf74d935 Mon Sep 17 00:00:00 2001 From: Igor Barakaiev Date: Wed, 26 Jun 2024 15:00:35 +0300 Subject: [PATCH 0524/1215] fix: configure_runtime/3 in ash_postgres.install (#335) --- lib/mix/tasks/ash_postgres.install.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 61991661..d5ad6401 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -14,7 +14,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> configure_config(otp_app, repo) |> configure_dev(otp_app, repo) |> configure_test(otp_app, repo) - |> configure_runtime(repo, otp_app) + |> configure_runtime(otp_app, repo) |> Igniter.Project.Application.add_new_child(repo) |> Igniter.add_task("ash.codegen", ["install_ash_postgres"]) end @@ -47,7 +47,7 @@ defmodule Mix.Tasks.AshPostgres.Install do For example: ecto://USER:PASS@HOST/DATABASE \"\"\" - config #{inspect(otp_app)}, Helpdesk.Repo, + config #{inspect(otp_app)}, #{inspect(repo), url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") end From 75a87e854aa8df3f065ce717ac3e0bd4b4934874 Mon Sep 17 00:00:00 2001 From: Igor Barakaiev Date: Wed, 26 Jun 2024 18:40:17 +0300 Subject: [PATCH 0525/1215] fix: typo in configure_runtime/3 (#336) --- lib/mix/tasks/ash_postgres.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index d5ad6401..9a835334 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -47,7 +47,7 @@ defmodule Mix.Tasks.AshPostgres.Install do For example: ecto://USER:PASS@HOST/DATABASE \"\"\" - config #{inspect(otp_app)}, #{inspect(repo), + config #{inspect(otp_app)}, #{inspect(repo)}, url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") end From c21cb69979069e34bdff745ddfd9cdf7cca42a0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 09:26:22 -0400 Subject: [PATCH 0526/1215] chore(deps): bump ash_sql in the production-dependencies group (#337) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.6 to 0.2.7 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.6...v0.2.7) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 365c80d3..babf23ca 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.0.16", "8eaebd5a9f3ee404937ac811a240799613b0619026e097436132d60eaf18ed16", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36c0d7653f7fb1d13cc03e1cc7ea7f6b9aadd278b9c9375ff5f0636ed0d7a785"}, - "ash_sql": {:hex, :ash_sql, "0.2.6", "097a191b138af6bd5104ae0e166db5deb443fe3a4616b349fffe98120382765d", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "076abb07d7762537880a12699b756c7c86c1a4e98fcd0dc8c8059de93f7e9265"}, + "ash_sql": {:hex, :ash_sql, "0.2.7", "56bfddcb4cf3edbbf702e2b665497309e43672fbf449ef049f4805211b9cd1b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "14622713cc08ede8fd0d2618b1718d759a6ee28839b8f738e6ee084703bd9437"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, @@ -37,12 +37,12 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, - "req": {:hex, :req, "0.5.0", "6d8a77c25cfc03e06a439fb12ffb51beade53e3fe0e2c5e362899a18b50298b3", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "dda04878c1396eebbfdec6db6f3d4ca609e5c8846b7ee88cc56eb9891406f7a3"}, + "req": {:hex, :req, "0.5.1", "90584216d064389a4ff2d4279fe2c11ff6c812ab00fa01a9fb9d15457f65ba70", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7ea96a1a95388eb0fefa92d89466cdfedba24032794e5c1147d78ec90db7edca"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"}, - "spark": {:hex, :spark, "2.2.4", "077363750eec4d80ffd4b20075676d17fce8bf82af1aa6aa51d2a539685b8d83", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "fd92bdd4508852bcd445c463d0536d0f130ed828d90401d17a061bcdeca4372a"}, + "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, + "spark": {:hex, :spark, "2.2.5", "3cc24cd72484d8aa87843ddeeda7e92def4c7a5608fcf4021ac2bc1ff27a7b26", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "cac25824be88e5baa4720d298e63aef5c0865b07cb5c4a6d9f8ee21e172f3f95"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 844d9b0717e2da64b45e5dd2706cf918739151b5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 27 Jun 2024 13:48:56 -0400 Subject: [PATCH 0527/1215] chore: fix 1.17 warning --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index feda08dc..b51da973 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1549,7 +1549,7 @@ defmodule AshPostgres.DataLayer do ecto_changeset = case changeset.data do %Ash.Changeset.OriginalDataNotAvailable{} -> - changeset.resource.__struct__ + changeset.resource.__struct__() data -> data From 58311f715241663fc7d1919b51d5990832596e69 Mon Sep 17 00:00:00 2001 From: Igor Barakaiev Date: Fri, 28 Jun 2024 00:05:53 +0300 Subject: [PATCH 0528/1215] fix: check for existing installed_extensions/0 in ash_postgres.install (#338) --- lib/mix/tasks/ash_postgres.install.ex | 28 +++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 9a835334..b3593096 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -217,9 +217,9 @@ defmodule Mix.Tasks.AshPostgres.Install do |> Sourceror.Zipper.top() |> use_ash_postgres_instead_of_ecto() |> Sourceror.Zipper.top() - |> add_installed_extensions_function() - |> Sourceror.Zipper.top() |> remove_adapter_option() + |> Sourceror.Zipper.top() + |> configure_installed_extensions_function() end) end @@ -270,15 +270,27 @@ defmodule Mix.Tasks.AshPostgres.Install do end end - defp add_installed_extensions_function(zipper) do + defp configure_installed_extensions_function(zipper) do case Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo) do {:ok, zipper} -> - Igniter.Code.Common.add_code(zipper, """ - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] + case Igniter.Code.Module.move_to_def(zipper, :installed_extensions, 0) do + {:ok, zipper} -> + case Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do + {:ok, zipper} -> + Igniter.Code.List.append_new_to_list(zipper, "ash-functions") + + :error -> + {:error, "installed_extensions/0 doesn't return a list"} + end + + _ -> + Igniter.Code.Common.add_code(zipper, """ + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """) end - """) _ -> zipper From ead7db2af7ab26d0e56e26170ef616e696631228 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Sat, 29 Jun 2024 16:20:41 +0300 Subject: [PATCH 0529/1215] improvement: order keys in snapshot json (#339) --- .../migration_generator.ex | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 85f4f7c0..e395c403 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -316,13 +316,10 @@ defmodule AshPostgres.MigrationGenerator do installed = Enum.map(requesteds, fn {name, _extension} -> name end) snapshot_contents = - Jason.encode!( - %{ - installed: installed - } - |> set_ash_functions(installed), - pretty: true - ) + %{installed: installed} + |> set_ash_functions(installed) + |> to_ordered_object() + |> Jason.encode!(pretty: true) contents = format(migration_file, contents, opts) @@ -3029,6 +3026,7 @@ defmodule AshPostgres.MigrationGenerator do %{identity | keys: keys} end) end) + |> to_ordered_object() |> Jason.encode!(pretty: true) end @@ -3313,4 +3311,15 @@ defmodule AshPostgres.MigrationGenerator do defp maybe_to_atom(value) when is_atom(value), do: value defp maybe_to_atom(value), do: String.to_atom(value) + + defp to_ordered_object(value) when is_map(value) do + value + |> Map.to_list() + |> List.keysort(0) + |> Enum.map(fn {key, value} -> {key, to_ordered_object(value)} end) + |> Jason.OrderedObject.new() + end + + defp to_ordered_object(value) when is_list(value), do: Enum.map(value, &to_ordered_object/1) + defp to_ordered_object(value), do: value end From e42665b14c82cff6f57bd09b18d00ecfa391427e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 1 Jul 2024 21:34:23 -0400 Subject: [PATCH 0530/1215] chore: update to latest igniter --- mix.exs | 1 + mix.lock | 8 +- .../test_no_sandbox_repo/extensions.json | 10 ++ .../multitenant_orgs/20240627223225.json | 83 ++++++++++++ .../20240627223224_install_5_extensions.exs | 120 ++++++++++++++++++ .../20240627223225_migrate_resources31.exs | 31 +++++ test/multitenancy_test.exs | 21 ++- test/support/multitenancy/resources/org.ex | 21 ++- 8 files changed, 288 insertions(+), 7 deletions(-) create mode 100644 priv/resource_snapshots/test_no_sandbox_repo/extensions.json create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json create mode 100644 priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs create mode 100644 priv/test_repo/migrations/20240627223225_migrate_resources31.exs diff --git a/mix.exs b/mix.exs index d09bb7a1..a75adc4c 100644 --- a/mix.exs +++ b/mix.exs @@ -164,6 +164,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.0 and >= 3.0.15")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.6")}, + {:igniter, "~> 0.2.5"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index babf23ca..cda8d477 100644 --- a/mix.lock +++ b/mix.lock @@ -22,15 +22,15 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, - "igniter": {:hex, :igniter, "0.2.3", "932295ef076390ee655dbf761f6cdcf3dc5c993439ac3c950de6d823ef45c4d1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.3", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "0e9702d58aa592594e5aad5ca28a50e7bd01d536152d0a64ea4bd3684e58aea1"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, + "igniter": {:hex, :igniter, "0.2.5", "c380ed5e2bffaf73691c330e39f9c21d62bc22ba2c84a59cefba8722be0e1f75", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ee47845498db546cd56df24f0d71444ba4a827d931b38e3244cf744c33577b52"}, + "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, diff --git a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json new file mode 100644 index 00000000..e084bbff --- /dev/null +++ b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json @@ -0,0 +1,10 @@ +{ + "installed": [ + "ash-functions", + "uuid-ossp", + "pg_trgm", + "citext", + "demo-functions_v1" + ], + "ash_functions_version": 3 +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json b/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json new file mode 100644 index 00000000..fe1362dd --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json @@ -0,0 +1,83 @@ +{ + "attributes": [ + { + "default": "fragment(\"gen_random_uuid()\")", + "size": null, + "type": "uuid", + "source": "id", + "references": null, + "primary_key?": true, + "allow_nil?": false, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "text", + "source": "name", + "references": null, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + }, + { + "default": "nil", + "size": null, + "type": "uuid", + "source": "owner_id", + "references": { + "name": "multitenant_orgs_owner_id_fkey", + "table": "users", + "multitenancy": { + "global": true, + "attribute": "org_id", + "strategy": "attribute" + }, + "destination_attribute": "id", + "primary_key?": true, + "schema": "public", + "on_delete": null, + "on_update": null, + "deferrable": false, + "match_with": null, + "match_type": null, + "index?": false, + "destination_attribute_default": null, + "destination_attribute_generated": null + }, + "primary_key?": false, + "allow_nil?": true, + "generated?": false + } + ], + "table": "multitenant_orgs", + "hash": "1346D9753C87606612C2B8191FB25221E5AE36698710A1182F9876FA9A6F2C5B", + "repo": "Elixir.AshPostgres.TestRepo", + "identities": [ + { + "name": "unique_by_name", + "keys": [ + { + "type": "atom", + "value": "name" + } + ], + "where": null, + "base_filter": null, + "all_tenants?": false, + "nils_distinct?": true, + "index_name": "multitenant_orgs_unique_by_name_index" + } + ], + "multitenancy": { + "global": true, + "attribute": "id", + "strategy": "attribute" + }, + "schema": null, + "check_constraints": [], + "custom_indexes": [], + "base_filter": null, + "custom_statements": [], + "has_create_action": true +} \ No newline at end of file diff --git a/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs b/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs new file mode 100644 index 00000000..09446bc3 --- /dev/null +++ b/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs @@ -0,0 +1,120 @@ +defmodule AshPostgres.TestNoSandboxRepo.Migrations.Install5Extensions20240627223222 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE($1, $2) $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS TRUE THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS NOT NULL THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) + RETURNS text[] AS $$ + DECLARE + start_index INT = 1; + end_index INT = array_length(arr, 1); + BEGIN + WHILE start_index <= end_index AND arr[start_index] = '' LOOP + start_index := start_index + 1; + END LOOP; + + WHILE end_index >= start_index AND arr[end_index] = '' LOOP + end_index := end_index - 1; + END LOOP; + + IF start_index > end_index THEN + RETURN ARRAY[]::text[]; + ELSE + RETURN arr[start_index : end_index]; + END IF; + END; $$ + LANGUAGE plpgsql + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """) + + execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"") + execute("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\"") + execute("CREATE EXTENSION IF NOT EXISTS \"citext\"") + + execute(""" + CREATE OR REPLACE FUNCTION ash_demo_functions() + RETURNS boolean AS $$ SELECT TRUE $$ + LANGUAGE SQL + IMMUTABLE; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute( + "DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])" + ) + + # execute("DROP EXTENSION IF EXISTS \"uuid-ossp\"") + # execute("DROP EXTENSION IF EXISTS \"pg_trgm\"") + # execute("DROP EXTENSION IF EXISTS \"citext\"") + execute(""" + DROP FUNCTION IF EXISTS ash_demo_functions() + """) + end +end diff --git a/priv/test_repo/migrations/20240627223225_migrate_resources31.exs b/priv/test_repo/migrations/20240627223225_migrate_resources31.exs new file mode 100644 index 00000000..8a90e3d4 --- /dev/null +++ b/priv/test_repo/migrations/20240627223225_migrate_resources31.exs @@ -0,0 +1,31 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources31 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(:multitenant_orgs) do + add( + :owner_id, + references(:users, + column: :id, + name: "multitenant_orgs_owner_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + end + + def down do + drop(constraint(:multitenant_orgs, "multitenant_orgs_owner_id_fkey")) + + alter table(:multitenant_orgs) do + remove(:owner_id) + end + end +end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index cb69da08..9b88b434 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -6,12 +6,12 @@ defmodule AshPostgres.Test.MultitenancyTest do setup do org1 = Org - |> Ash.Changeset.for_create(:create, %{name: "test1"}) + |> Ash.Changeset.for_create(:create, %{name: "test1"}, authorize?: false) |> Ash.create!() org2 = Org - |> Ash.Changeset.for_create(:create, %{name: "test2"}) + |> Ash.Changeset.for_create(:create, %{name: "test2"}, authorize?: false) |> Ash.create!() [org1: org1, org2: org2] @@ -37,6 +37,23 @@ defmodule AshPostgres.Test.MultitenancyTest do |> Ash.read!() end + test "attribute multitenancy works with authorization", %{org1: org1} do + user = + User + |> Ash.Changeset.new() + |> Ash.Changeset.manage_relationship(:org, org1, type: :append_and_remove) + |> Ash.create!() + + Logger.configure(level: :debug) + + assert [] = + Org + |> Ash.Query.set_tenant(tenant(org1)) + |> Ash.Query.for_read(:has_policies, %{}, actor: user, authorize?: true) + |> Ash.read!() + |> IO.inspect() + end + test "context multitenancy works with policies", %{org1: org1} do post = Post diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index 1ac913ab..b155dde0 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -2,7 +2,18 @@ defmodule AshPostgres.MultitenancyTest.Org do @moduledoc false use Ash.Resource, domain: AshPostgres.MultitenancyTest.Domain, - data_layer: AshPostgres.DataLayer + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + policies do + policy action(:has_policies) do + authorize_if(relates_to_actor_via(:owner)) + end + + # policy always() do + # authorize_if(always()) + # end + end identities do identity(:unique_by_name, [:name]) @@ -17,6 +28,8 @@ defmodule AshPostgres.MultitenancyTest.Org do default_accept(:*) defaults([:create, :read, :update, :destroy]) + + read(:has_policies) end postgres do @@ -36,6 +49,12 @@ defmodule AshPostgres.MultitenancyTest.Org do end relationships do + belongs_to :owner, AshPostgres.MultitenancyTest.User do + attribute_public?(false) + public?(false) + attribute_type(:string) + end + has_many(:posts, AshPostgres.MultitenancyTest.Post, destination_attribute: :org_id, public?: true From 185e9bea832707235f2ae2d8a393e4f553e8209b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Jul 2024 12:30:25 -0400 Subject: [PATCH 0531/1215] test: adjustments for tests --- config/config.exs | 4 ++++ test/multitenancy_test.exs | 3 --- test/support/multitenancy/domain.ex | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config/config.exs b/config/config.exs index 24a02437..2b5d4262 100644 --- a/config/config.exs +++ b/config/config.exs @@ -55,5 +55,9 @@ if Mix.env() == :test do AshPostgres.Test.ComplexCalculations.Domain ] + config :ash, :compatible_foreign_key_types, [ + {Ash.Type.String, Ash.Type.UUID} + ] + config :logger, level: :warning end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 9b88b434..5d107f2b 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -44,14 +44,11 @@ defmodule AshPostgres.Test.MultitenancyTest do |> Ash.Changeset.manage_relationship(:org, org1, type: :append_and_remove) |> Ash.create!() - Logger.configure(level: :debug) - assert [] = Org |> Ash.Query.set_tenant(tenant(org1)) |> Ash.Query.for_read(:has_policies, %{}, actor: user, authorize?: true) |> Ash.read!() - |> IO.inspect() end test "context multitenancy works with policies", %{org1: org1} do diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 664b3919..68a5d9de 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -8,4 +8,8 @@ defmodule AshPostgres.MultitenancyTest.Domain do resource(AshPostgres.MultitenancyTest.Post) resource(AshPostgres.MultitenancyTest.PostLink) end + + authorization do + authorize(:when_requested) + end end From 7d41e69a112170baf9b285314c42c69ebf4730ab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Jul 2024 12:46:03 -0400 Subject: [PATCH 0532/1215] chore: gen migrations --- .../multitenant_orgs/20240702164513.json | 83 +++++++++++++++++++ .../20240702164513_migrate_resources32.exs | 23 +++++ 2 files changed, 106 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json create mode 100644 priv/test_repo/migrations/20240702164513_migrate_resources32.exs diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json b/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json new file mode 100644 index 00000000..a7d1b16c --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json @@ -0,0 +1,83 @@ +{ + "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": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "org_id", + "global": true, + "strategy": "attribute" + }, + "name": "multitenant_orgs_owner_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "users" + }, + "size": null, + "source": "owner_id", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "8A0A6960023A27597EB918B7EDCA957E5AA9C78D3BE83FE7924A1F5BFA531F6C", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "multitenant_orgs_unique_by_name_index", + "keys": [ + { + "type": "atom", + "value": "name" + } + ], + "name": "unique_by_name", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "multitenant_orgs" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240702164513_migrate_resources32.exs b/priv/test_repo/migrations/20240702164513_migrate_resources32.exs new file mode 100644 index 00000000..fed5ff72 --- /dev/null +++ b/priv/test_repo/migrations/20240702164513_migrate_resources32.exs @@ -0,0 +1,23 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources32 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(:multitenant_orgs) do + remove(:owner_id) + add(:owner_id, :text) + end + end + + def down do + alter table(:multitenant_orgs) do + remove(:owner_id) + add(:owner_id, :uuid) + end + end +end From ffd3ff5a81042752c8f79b6c826c02fc0dc41ef5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Jul 2024 13:01:54 -0400 Subject: [PATCH 0533/1215] test: rollback change for uuid -> string type in tests --- .../20240702164513_migrate_resources32.exs | 23 ------------------- test/support/multitenancy/resources/org.ex | 1 - 2 files changed, 24 deletions(-) delete mode 100644 priv/test_repo/migrations/20240702164513_migrate_resources32.exs diff --git a/priv/test_repo/migrations/20240702164513_migrate_resources32.exs b/priv/test_repo/migrations/20240702164513_migrate_resources32.exs deleted file mode 100644 index fed5ff72..00000000 --- a/priv/test_repo/migrations/20240702164513_migrate_resources32.exs +++ /dev/null @@ -1,23 +0,0 @@ -defmodule AshPostgres.TestRepo.Migrations.MigrateResources32 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(:multitenant_orgs) do - remove(:owner_id) - add(:owner_id, :text) - end - end - - def down do - alter table(:multitenant_orgs) do - remove(:owner_id) - add(:owner_id, :uuid) - end - end -end diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index b155dde0..3d1353c8 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -52,7 +52,6 @@ defmodule AshPostgres.MultitenancyTest.Org do belongs_to :owner, AshPostgres.MultitenancyTest.User do attribute_public?(false) public?(false) - attribute_type(:string) end has_many(:posts, AshPostgres.MultitenancyTest.Post, From e42be3818361172551a0984d232a27eb9e132f71 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 2 Jul 2024 18:07:50 -0400 Subject: [PATCH 0534/1215] chore: update to latest igniter goodies --- lib/data_layer.ex | 6 +++--- lib/mix/tasks/ash_postgres.install.ex | 28 ++++++++++++++------------- mix.exs | 2 +- mix.lock | 4 ++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b51da973..488e9fe9 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2922,7 +2922,7 @@ defmodule AshPostgres.DataLayer do end if Code.ensure_loaded?(Igniter) do - def install(igniter, module, Ash.Resource, path, _argv) do + def install(igniter, module, Ash.Resource, _path, _argv) do table_name = module |> Module.split() @@ -2932,8 +2932,8 @@ defmodule AshPostgres.DataLayer do repo = Igniter.Code.Module.module_name("Repo") igniter - |> Spark.Igniter.set_option(Ash.Resource, path, [:postgres, :table], table_name) - |> Spark.Igniter.set_option(Ash.Resource, path, [:postgres, :repo], repo) + |> Spark.Igniter.set_option(module, [:postgres, :table], table_name) + |> Spark.Igniter.set_option(module, [:postgres, :repo], repo) end def install(igniter, _, _, _), do: igniter diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index b3593096..bf4a05c5 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -196,8 +196,6 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp setup_repo_module(igniter, otp_app, repo) do - path = Igniter.Code.Module.proper_location(repo) - default_repo_contents = """ defmodule #{inspect(repo)} do @@ -210,17 +208,21 @@ defmodule Mix.Tasks.AshPostgres.Install do end """ - igniter - |> Igniter.create_or_update_elixir_file(path, default_repo_contents, fn zipper -> - zipper - |> set_otp_app(otp_app) - |> Sourceror.Zipper.top() - |> use_ash_postgres_instead_of_ecto() - |> Sourceror.Zipper.top() - |> remove_adapter_option() - |> Sourceror.Zipper.top() - |> configure_installed_extensions_function() - end) + Igniter.Code.Module.find_and_update_or_create_module( + igniter, + repo, + default_repo_contents, + fn zipper -> + zipper + |> set_otp_app(otp_app) + |> Sourceror.Zipper.top() + |> use_ash_postgres_instead_of_ecto() + |> Sourceror.Zipper.top() + |> remove_adapter_option() + |> Sourceror.Zipper.top() + |> configure_installed_extensions_function() + end + ) end defp use_ash_postgres_instead_of_ecto(zipper) do diff --git a/mix.exs b/mix.exs index a75adc4c..e8fbf66f 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.0 and >= 3.0.15")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.6")}, - {:igniter, "~> 0.2.5"}, + {:igniter, "~> 0.2.6"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index cda8d477..575f5658 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,7 @@ "ash_sql": {:hex, :ash_sql, "0.2.7", "56bfddcb4cf3edbbf702e2b665497309e43672fbf449ef049f4805211b9cd1b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "14622713cc08ede8fd0d2618b1718d759a6ee28839b8f738e6ee084703bd9437"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, + "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, - "igniter": {:hex, :igniter, "0.2.5", "c380ed5e2bffaf73691c330e39f9c21d62bc22ba2c84a59cefba8722be0e1f75", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ee47845498db546cd56df24f0d71444ba4a827d931b38e3244cf744c33577b52"}, + "igniter": {:hex, :igniter, "0.2.6", "472a4b97c779dd9f30d3947e23217a7e20bff840fde83a16ca70ce96ca76a803", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a234c958b90f152fd4145ebaa6463731e24a9bb83749586ac890e37b9868c1c6"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, From bc42cce55bb4fd69f406a619afdec4abd716e785 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 3 Jul 2024 11:52:01 -0400 Subject: [PATCH 0535/1215] chore: generate migrations --- mix.lock | 2 +- .../multitenant_orgs/20240703155134.json | 83 +++++++++++++++++++ .../20240703155134_migrate_resources32.exs | 21 +++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json create mode 100644 priv/test_repo/migrations/20240703155134_migrate_resources32.exs diff --git a/mix.lock b/mix.lock index 575f5658..32333712 100644 --- a/mix.lock +++ b/mix.lock @@ -42,7 +42,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, - "spark": {:hex, :spark, "2.2.5", "3cc24cd72484d8aa87843ddeeda7e92def4c7a5608fcf4021ac2bc1ff27a7b26", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "cac25824be88e5baa4720d298e63aef5c0865b07cb5c4a6d9f8ee21e172f3f95"}, + "spark": {:hex, :spark, "2.2.7", "96113e09a52a2a95fd696e06f310950132aabfacf5c7b34e0666d26ce4a7b7a7", [:mix], [{:igniter, "~> 0.2.6", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e192add56a260382d4d270e1490401786f96545b86d67b466544cecb48c3f9a4"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json b/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json new file mode 100644 index 00000000..fe64338e --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json @@ -0,0 +1,83 @@ +{ + "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": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "org_id", + "global": true, + "strategy": "attribute" + }, + "name": "multitenant_orgs_owner_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "users" + }, + "size": null, + "source": "owner_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "18F627A5130D3D2AED877928324A6A9310B894EFB30F35CA8A2136B5CB0E42A7", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "multitenant_orgs_unique_by_name_index", + "keys": [ + { + "type": "atom", + "value": "name" + } + ], + "name": "unique_by_name", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "multitenant_orgs" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240703155134_migrate_resources32.exs b/priv/test_repo/migrations/20240703155134_migrate_resources32.exs new file mode 100644 index 00000000..47f472a2 --- /dev/null +++ b/priv/test_repo/migrations/20240703155134_migrate_resources32.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources32 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(:multitenant_orgs) do + modify(:owner_id, :uuid) + end + end + + def down do + alter table(:multitenant_orgs) do + modify(:owner_id, :text) + end + end +end From ffbfc1c23abb646dcb304e085e99d99b399a6096 Mon Sep 17 00:00:00 2001 From: Igor Barakaiev Date: Wed, 3 Jul 2024 18:12:37 +0200 Subject: [PATCH 0536/1215] fix: default_repo_contents in ash_postgres.install (#340) --- lib/mix/tasks/ash_postgres.install.ex | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index bf4a05c5..a0096840 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -198,13 +198,11 @@ defmodule Mix.Tasks.AshPostgres.Install do defp setup_repo_module(igniter, otp_app, repo) do default_repo_contents = """ - defmodule #{inspect(repo)} do - use AshPostgres.Repo, otp_app: #{inspect(otp_app)} + use AshPostgres.Repo, otp_app: #{inspect(otp_app)} - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] - end + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] end """ From afe4a657e9911c43df8edfb0cb40062a3b4d3b47 Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:45:42 +0800 Subject: [PATCH 0537/1215] docs: Add note that `nulls_distinct` requires PostgreSQL 15 (#341) Unfortunately it's not an option on indexes in PostgreSQL 14 :( https://www.postgresql.org/docs/14/sql-createindex.html --- lib/custom_index.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 4b77cae3..3a4e6a8c 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -62,7 +62,7 @@ defmodule AshPostgres.CustomIndex do ], nulls_distinct: [ type: :boolean, - doc: "specify whether null values should be considered distinct for a unique index.", + doc: "specify whether null values should be considered distinct for a unique index. Requires PostgreSQL 15 or later", default: true ], message: [ From 3741bbc0b30d31c87cab96b276242f47fccb7610 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Fri, 5 Jul 2024 18:29:23 +0200 Subject: [PATCH 0538/1215] chore: fix 1.17 warning (#343) --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 488e9fe9..ff14f31e 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1263,7 +1263,7 @@ defmodule AshPostgres.DataLayer do ecto_changeset = case changeset.data do %Ash.Changeset.OriginalDataNotAvailable{} -> - changeset.resource.__struct__ + changeset.resource.__struct__() data -> data From f0d07a3a4db5b9c6488221a33820be384786f9e8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 6 Jul 2024 06:38:06 -0400 Subject: [PATCH 0539/1215] fix: ensure that `from_many?` relationships in lateral join are limited --- lib/data_layer.ex | 7 +++++++ mix.lock | 2 +- test/load_test.exs | 29 +++++++++++++++++++++++++++++ test/support/resources/comment.ex | 13 +++++++++++++ test/support/resources/post.ex | 6 ++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ff14f31e..a1813511 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -995,6 +995,13 @@ defmodule AshPostgres.DataLayer do query end + base_query = + if Map.get(relationship, :from_many?) do + from(row in base_query, limit: 1) + else + base_query + end + base_query = cond do Map.get(relationship, :manual) -> diff --git a/mix.lock b/mix.lock index 32333712..c9451722 100644 --- a/mix.lock +++ b/mix.lock @@ -29,7 +29,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, diff --git a/test/load_test.exs b/test/load_test.exs index 4ab6d325..f37920e0 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -24,6 +24,35 @@ defmodule AshPostgres.Test.LoadTest do assert [%Post{comments: [%{title: "match"}]}] = results end + test "has_one relationships properly limit in the data layer" do + assert %Post{comments: %Ash.NotLoaded{type: :relationship}} = + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + :timer.sleep(1) + + assert %{id: second_comment_id} = + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Logger.configure(level: :debug) + + assert %{id: ^second_comment_id} = + Post + |> Ash.Query.load(latest_comment: [:expects_only_one_comment]) + |> Ash.read_one!() + |> Map.get(:latest_comment) + end + test "belongs_to relationships can be loaded" do assert %Comment{post: %Ash.NotLoaded{type: :relationship}} = comment = diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index 4253fbc9..82bccc9a 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -50,6 +50,19 @@ defmodule AshPostgres.Test.Comment do list(:posts_for_comments_containing_title, [:post, :comments_containing_title, :post], :title) end + calculations do + calculate(:expects_only_one_comment, :string, fn records, _ -> + if Enum.count(records) > 1 do + raise "expected only one comment" + end + + {:ok, + Enum.map(records, fn _ -> + "hello" + end)} + end) + end + relationships do belongs_to(:post, AshPostgres.Test.Post) do public?(true) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c59dcfaf..e9f46e52 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -306,6 +306,12 @@ defmodule AshPostgres.Test.Post do has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id, public?: true) + has_one :latest_comment, AshPostgres.Test.Comment do + sort(created_at: :desc) + from_many?(true) + public?(true) + end + has_many :comments_matching_post_title, AshPostgres.Test.Comment do public?(true) filter(expr(title == parent_expr(title))) From 3385bd21f10ec9878248b5f1e2acb2fd98d4105c Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Mon, 8 Jul 2024 15:54:50 +0200 Subject: [PATCH 0540/1215] improvement: add storage type option (#342) --------- Co-authored-by: Zach Daniel --- .formatter.exs | 1 + .../dsls/DSL:-AshPostgres.DataLayer.md | 3 +- lib/custom_index.ex | 3 +- lib/data_layer.ex | 6 ++ lib/data_layer/info.ex | 5 ++ lib/sql_implementation.ex | 24 ++++++ mix.exs | 2 +- mix.lock | 4 +- .../test_repo/authors/20240705113722.json | 82 +++++++++++++++++++ .../20240705113722_migrate_resources33.exs | 21 +++++ test/storage_types_test.exs | 45 ++++++++++ test/support/resources/author.ex | 4 + 12 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/authors/20240705113722.json create mode 100644 priv/test_repo/migrations/20240705113722_migrate_resources33.exs create mode 100644 test/storage_types_test.exs diff --git a/.formatter.exs b/.formatter.exs index 8211617e..b9c6f498 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -43,6 +43,7 @@ spark_locals_without_parens = [ skip_unique_indexes: 1, statement: 1, statement: 2, + storage_types: 1, table: 1, template: 1, unique: 1, diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index 2579f609..bc5f18c8 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -40,6 +40,7 @@ end |------|------|---------|------| | [`repo`](#postgres-repo){: #postgres-repo .spark-required} | `module \| (any, any -> any)` | | The repo that will be used to fetch your data. See the `AshPostgres.Repo` documentation for more. Can also be a function that takes a resource and a type `:read \| :mutate` and returns the repo | | [`migrate?`](#postgres-migrate?){: #postgres-migrate? } | `boolean` | `true` | Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations` | +| [`storage_types`](#postgres-storage_types){: #postgres-storage_types } | `keyword` | `[]` | A keyword list of attribute names to the ecto type that should be used for that attribute. Only necessary if you need to override the defaults. | | [`migration_types`](#postgres-migration_types){: #postgres-migration_types } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | | [`migration_defaults`](#postgres-migration_defaults){: #postgres-migration_defaults } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | | [`calculations_to_sql`](#postgres-calculations_to_sql){: #postgres-calculations_to_sql } | `keyword` | | A keyword list of calculations and their SQL representation. Used when creating unique indexes for identities over calculations | @@ -114,7 +115,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" | [`prefix`](#postgres-custom_indexes-index-prefix){: #postgres-custom_indexes-index-prefix } | `String.t` | | specify an optional prefix for the index. | | [`where`](#postgres-custom_indexes-index-where){: #postgres-custom_indexes-index-where } | `String.t` | | specify conditions for a partial index. | | [`include`](#postgres-custom_indexes-index-include){: #postgres-custom_indexes-index-include } | `list(String.t)` | | specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs. | -| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | `true` | specify whether null values should be considered distinct for a unique index. | +| [`nulls_distinct`](#postgres-custom_indexes-index-nulls_distinct){: #postgres-custom_indexes-index-nulls_distinct } | `boolean` | `true` | specify whether null values should be considered distinct for a unique index. Requires PostgreSQL 15 or later | | [`message`](#postgres-custom_indexes-index-message){: #postgres-custom_indexes-index-message } | `String.t` | | A custom message to use for unique indexes that have been violated | | [`all_tenants?`](#postgres-custom_indexes-index-all_tenants?){: #postgres-custom_indexes-index-all_tenants? } | `boolean` | `false` | Whether or not the index should factor in the multitenancy attribute or not. | diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 3a4e6a8c..9d037331 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -62,7 +62,8 @@ defmodule AshPostgres.CustomIndex do ], nulls_distinct: [ type: :boolean, - doc: "specify whether null values should be considered distinct for a unique index. Requires PostgreSQL 15 or later", + doc: + "specify whether null values should be considered distinct for a unique index. Requires PostgreSQL 15 or later", default: true ], message: [ diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a1813511..d218f9cf 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -287,6 +287,12 @@ defmodule AshPostgres.DataLayer do doc: "Whether or not to include this resource in the generated migrations with `mix ash.generate_migrations`" ], + storage_types: [ + type: :keyword_list, + default: [], + doc: + "A keyword list of attribute names to the ecto type that should be used for that attribute. Only necessary if you need to override the defaults." + ], migration_types: [ type: :keyword_list, default: [], diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index b16c252f..5dcec0ad 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -78,6 +78,11 @@ defmodule AshPostgres.DataLayer.Info do Extension.get_opt(resource, [:postgres], :migration_types, []) end + @doc "A keyword list of customized storage types" + def storage_types(resource) do + Extension.get_opt(resource, [:postgres], :storage_types, []) + end + @doc "A keyword list of customized migration defaults" def migration_defaults(resource) do Extension.get_opt(resource, [:postgres], :migration_defaults, []) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 61ea99c0..836cc93b 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -17,6 +17,30 @@ defmodule AshPostgres.SqlImplementation do def require_extension_for_citext, do: {true, "citext"} @impl true + def storage_type(resource, field) do + case AshPostgres.DataLayer.Info.storage_types(resource)[field] do + nil -> + nil + + {:array, type} -> + parameterized_type({:array, Ash.Type.get_type(type)}, [], false) + + {:array, type, constraints} -> + parameterized_type({:array, Ash.Type.get_type(type)}, constraints, false) + + {type, constraints} -> + parameterized_type(type, constraints, false) + + type -> + parameterized_type(type, [], false) + end + end + + @impl true + def expr(_query, [], _bindings, _embedded?, acc, type) when type in [:map, :jsonb] do + {:ok, Ecto.Query.dynamic(fragment("'[]'::jsonb")), acc} + end + def expr( query, %like{arguments: [arg1, arg2], embedded?: pred_embedded?}, diff --git a/mix.exs b/mix.exs index e8fbf66f..5c37afa0 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.0 and >= 3.0.15")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.6")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.8")}, {:igniter, "~> 0.2.6"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index c9451722..1151b00b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,12 +1,12 @@ %{ "ash": {:hex, :ash, "3.0.16", "8eaebd5a9f3ee404937ac811a240799613b0619026e097436132d60eaf18ed16", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36c0d7653f7fb1d13cc03e1cc7ea7f6b9aadd278b9c9375ff5f0636ed0d7a785"}, - "ash_sql": {:hex, :ash_sql, "0.2.7", "56bfddcb4cf3edbbf702e2b665497309e43672fbf449ef049f4805211b9cd1b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "14622713cc08ede8fd0d2618b1718d759a6ee28839b8f738e6ee084703bd9437"}, + "ash_sql": {:hex, :ash_sql, "0.2.8", "ad20dc5487b68a095a12f84918d8ffab4bef12de59b7c5cf962a7c1dc03f5d81", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d120568a19f3d72a634c3bfa9ce5fa0fae1d3c44ee4cdb8c78f151e490bf849b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, diff --git a/priv/resource_snapshots/test_repo/authors/20240705113722.json b/priv/resource_snapshots/test_repo/authors/20240705113722.json new file mode 100644 index 00000000..c5e6fdf7 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20240705113722.json @@ -0,0 +1,82 @@ +{ + "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": "first_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "last_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "bio", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "bios", + "type": "jsonb" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "badges", + "type": [ + "array", + "text" + ] + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "D0080403BD79419DCA499838D2BE1F8AE2744F28A2ADC775B74D8EDA0EC11DB1", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "authors" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240705113722_migrate_resources33.exs b/priv/test_repo/migrations/20240705113722_migrate_resources33.exs new file mode 100644 index 00000000..e06ec7c7 --- /dev/null +++ b/priv/test_repo/migrations/20240705113722_migrate_resources33.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources33 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(:authors) do + add(:bios, :jsonb) + end + end + + def down do + alter table(:authors) do + remove(:bios) + end + end +end diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs new file mode 100644 index 00000000..dde647b3 --- /dev/null +++ b/test/storage_types_test.exs @@ -0,0 +1,45 @@ +defmodule AshPostgres.StorageTypesTest do + use AshPostgres.RepoCase, async: false + + alias Ash.BulkResult + alias AshPostgres.Test.Author + + require Ash.Query + + test "can save {:array, :map} as jsonb" do + %{id: id} = + Author + |> Ash.Changeset.for_create( + :create, + %{bios: [%{title: "bio1"}, %{title: "bio2"}]} + ) + |> Ash.create!() + + # testing empty list edge case + %BulkResult{records: [author]} = + Author + |> Ash.Query.filter(id == ^id) + |> Ash.bulk_update(:update, %{bios: []}, + return_errors?: true, + notify?: true, + strategy: [:atomic, :stream, :atomic_batches], + allow_stream_with: :full_read, + return_records?: true + ) + + assert author.bios == [] + + %BulkResult{records: [author]} = + Author + |> Ash.Query.filter(id == ^id) + |> Ash.bulk_update(:update, %{bios: [%{a: 1}]}, + return_errors?: true, + notify?: true, + strategy: [:atomic, :stream, :atomic_batches], + allow_stream_with: :full_read, + return_records?: true + ) + + assert author.bios == [%{"a" => 1}] + end +end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 170cb98c..dd358005 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -18,6 +18,9 @@ defmodule AshPostgres.Test.Author do postgres do table("authors") repo(AshPostgres.TestRepo) + + migration_types bios: :jsonb + storage_types(bios: :jsonb) end attributes do @@ -25,6 +28,7 @@ defmodule AshPostgres.Test.Author do attribute(:first_name, :string, public?: true) attribute(:last_name, :string, public?: true) attribute(:bio, AshPostgres.Test.Bio, public?: true) + attribute(:bios, {:array, :map}, public?: true) attribute(:badges, {:array, :atom}, public?: true) end From d293d0f3024ccbebe495f7868daa10517f2ba2c4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 8 Jul 2024 15:56:58 -0400 Subject: [PATCH 0541/1215] improvement: use `Ash.Igniter.codegen` --- lib/mix/tasks/ash_postgres.install.ex | 2 +- test/load_test.exs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index a0096840..98cd1b9e 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -16,7 +16,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> configure_test(otp_app, repo) |> configure_runtime(otp_app, repo) |> Igniter.Project.Application.add_new_child(repo) - |> Igniter.add_task("ash.codegen", ["install_ash_postgres"]) + |> Ash.Igniter.codegen("install_ash_postgres") end defp configure_config(igniter, otp_app, repo) do diff --git a/test/load_test.exs b/test/load_test.exs index f37920e0..dd9b0e9d 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -44,8 +44,6 @@ defmodule AshPostgres.Test.LoadTest do |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.create!() - Logger.configure(level: :debug) - assert %{id: ^second_comment_id} = Post |> Ash.Query.load(latest_comment: [:expects_only_one_comment]) From 24576c168c51550b17a0c11b7ec1d2a33792eca9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 07:54:50 -0400 Subject: [PATCH 0542/1215] fix: ensure that we return `{:ok, zipper}` from `find_and_update_or_create_module` closes #345 --- lib/mix/tasks/ash_postgres.install.ex | 1 + test/atomics_test.exs | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 98cd1b9e..da6fbd21 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -219,6 +219,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> remove_adapter_option() |> Sourceror.Zipper.top() |> configure_installed_extensions_function() + |> then(&{:ok, &1}) end ) end diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 95d4712c..573b7f21 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -119,9 +119,10 @@ defmodule AshPostgres.AtomicsTest do |> Ash.create!() # just asserting that there is no exception here - post - |> Ash.Changeset.for_update(:change_title_to_foo_unless_its_already_foo) - |> Ash.update!() + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.limit(1) + |> Ash.bulk_update!(:change_title_to_foo_unless_its_already_foo, %{}) end test "an atomic works with a datetime" do From 1f370b6357a6173a7691b1ce324894aacb9a4422 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 11:19:46 -0400 Subject: [PATCH 0543/1215] docs: update readme with note on version support --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2d9bb79d..fa7bcfc3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). +> ### What versions are supported? {: .info} +> +> Any version higher than 13 is fully supported. Versions lower than this can be made to work, but certain edge cases may need to be manually handled. This becomes more and more true the further back in versions that you go. + ## Tutorials - [Get Started](documentation/tutorials/get-started-with-ash-postgres.md) From a930457d87347c4b5f120814b6ca72d2c6deb5f8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 11:20:36 -0400 Subject: [PATCH 0544/1215] docs: move new note to what-is-ash-postgres.md --- README.md | 4 ---- .../topics/about-ash-postgres/what-is-ash-postgres.md | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fa7bcfc3..2d9bb79d 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,6 @@ Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). -> ### What versions are supported? {: .info} -> -> Any version higher than 13 is fully supported. Versions lower than this can be made to work, but certain edge cases may need to be manually handled. This becomes more and more true the further back in versions that you go. - ## Tutorials - [Get Started](documentation/tutorials/get-started-with-ash-postgres.md) diff --git a/documentation/topics/about-ash-postgres/what-is-ash-postgres.md b/documentation/topics/about-ash-postgres/what-is-ash-postgres.md index 253c4df3..883b3512 100644 --- a/documentation/topics/about-ash-postgres/what-is-ash-postgres.md +++ b/documentation/topics/about-ash-postgres/what-is-ash-postgres.md @@ -2,6 +2,10 @@ AshPostgres is the PostgreSQL `Ash.DataLayer` for [Ash Framework](https://hexdocs.pm/ash). This is the most fully-featured Ash data layer, and unless you need a specific characteristic or feature of another data layer, you should use `AshPostgres`. +> ### What versions are supported? {: .info} +> +> Any version higher than 13 is fully supported. Versions lower than this can be made to work, but certain edge cases may need to be manually handled. This becomes more and more true the further back in versions that you go. + Use this to persist records in a PostgreSQL table or view. For example, the resource below would be persisted in a table called `tweets`: ```elixir From 7dae2408d4676f5d4b23f654947f9239541697fc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 17:43:06 -0400 Subject: [PATCH 0545/1215] chore: choose a shorter name for mix task --- lib/mix/tasks/ash_postgres.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index da6fbd21..364a315b 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -16,7 +16,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> configure_test(otp_app, repo) |> configure_runtime(otp_app, repo) |> Igniter.Project.Application.add_new_child(repo) - |> Ash.Igniter.codegen("install_ash_postgres") + |> Ash.Igniter.codegen("initialize") end defp configure_config(igniter, otp_app, repo) do From edb24ef923da465bb3616c8fa6a1320d86705cd6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 19:57:04 -0400 Subject: [PATCH 0546/1215] chore: update to ash 3.1 --- mix.exs | 4 ++-- mix.lock | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 5c37afa0..d7ee138e 100644 --- a/mix.exs +++ b/mix.exs @@ -162,9 +162,9 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.0 and >= 3.0.15")}, + {:ash, ash_version("~> 3.1")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.8")}, - {:igniter, "~> 0.2.6"}, + {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 1151b00b..b4def659 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.0.16", "8eaebd5a9f3ee404937ac811a240799613b0619026e097436132d60eaf18ed16", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36c0d7653f7fb1d13cc03e1cc7ea7f6b9aadd278b9c9375ff5f0636ed0d7a785"}, + "ash": {:hex, :ash, "3.1.0", "ad8ccd2271a0802b1a657c3558b4ace005ace46ddb3cf262713c891e5b0bdd6b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.7", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "04b311a93127dc2ac84aeca0ff45c43236e01dfe120ede2c15d5827eef2194c0"}, "ash_sql": {:hex, :ash_sql, "0.2.8", "ad20dc5487b68a095a12f84918d8ffab4bef12de59b7c5cf962a7c1dc03f5d81", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d120568a19f3d72a634c3bfa9ce5fa0fae1d3c44ee4cdb8c78f151e490bf849b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, - "igniter": {:hex, :igniter, "0.2.6", "472a4b97c779dd9f30d3947e23217a7e20bff840fde83a16ca70ce96ca76a803", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a234c958b90f152fd4145ebaa6463731e24a9bb83749586ac890e37b9868c1c6"}, + "igniter": {:hex, :igniter, "0.2.9", "7ef177063b417b6efbc74e57f05b5f3aad65b783f345df03462a2d5547d2bfa9", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "659e36a263325e7f87a8b1b09ef2387308a2ff12b917c27012cbaa0c46c957c9"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -37,7 +37,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, - "req": {:hex, :req, "0.5.1", "90584216d064389a4ff2d4279fe2c11ff6c812ab00fa01a9fb9d15457f65ba70", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7ea96a1a95388eb0fefa92d89466cdfedba24032794e5c1147d78ec90db7edca"}, + "req": {:hex, :req, "0.5.2", "70b4976e5fbefe84e5a57fd3eea49d4e9aa0ac015301275490eafeaec380f97f", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c63539ab4c2d6ced6114d2684276cef18ac185ee00674ee9af4b1febba1f986"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, From 72b029be218e2b4c8d611f8490f2a697e1aa51ab Mon Sep 17 00:00:00 2001 From: Alessio Montagnani Date: Wed, 10 Jul 2024 01:57:38 +0200 Subject: [PATCH 0547/1215] improvement: add support for `:uuid_v7` type (#333) --- lib/migration_generator/ash_functions.ex | 79 +++++++++++++++++-- .../migration_generator.ex | 4 + .../test_repo/extensions.json | 2 +- ...2715_install_ash-functions_extension_4.exs | 54 +++++++++++++ test/migration_generator_test.exs | 10 +++ 5 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 957f8689..04eefa24 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do - @latest_version 3 + @latest_version 4 def latest_version, do: @latest_version @@ -66,6 +66,8 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do \"\"\") #{ash_raise_error()} + + #{uuid_generate_v7()} """ end @@ -89,6 +91,8 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do #{ash_raise_error()} + #{uuid_generate_v7()} + execute(\"\"\" CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) RETURNS text[] AS $$ @@ -117,27 +121,47 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do end def install(1) do - ash_raise_error() + """ + #{ash_raise_error()} + + #{uuid_generate_v7()} + """ end def install(2) do - ash_raise_error() + """ + #{ash_raise_error()} + + #{uuid_generate_v7()} + """ + end + + def install(3) do + uuid_generate_v7() + end + + def drop(3) do + "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)\")" end def drop(2) do - ash_raise_error(false) + """ + #{ash_raise_error()} + + "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)\")" + """ end def drop(1) do - "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)\")" + "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid), ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE)\")" end def drop(0) do - "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" + "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid), ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" end def drop(nil) do - "execute(\"DROP FUNCTION IF EXISTS ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" + "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid), ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" end defp ash_raise_error(prefix? \\ true) do @@ -174,4 +198,45 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do \"\"\") """ end + + defp uuid_generate_v7 do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION uuid_generate_v7() + RETURNS UUID + AS $$ + DECLARE + timestamp TIMESTAMPTZ; + microseconds INT; + BEGIN + timestamp = clock_timestamp(); + microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; + + RETURN encode( + set_byte( + set_byte( + overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 + ), + 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int + ), + 7, microseconds::bit(8)::int + ), + 'hex')::UUID; + END + $$ + LANGUAGE PLPGSQL + VOLATILE; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) + RETURNS TIMESTAMP WITHOUT TIME ZONE + AS $$ + SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); + $$ + LANGUAGE SQL + IMMUTABLE PARALLEL SAFE STRICT LEAKPROOF; + \"\"\") + """ + end end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index e395c403..a6138ebc 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2848,6 +2848,7 @@ defmodule AshPostgres.MigrationGenerator do defp migration_type(Ash.Type.CiString, _), do: :citext defp migration_type(Ash.Type.UUID, _), do: :uuid + defp migration_type(Ash.Type.UUIDv7, _), do: :uuid defp migration_type(Ash.Type.Integer, _), do: :bigint defp migration_type(other, constraints) do @@ -2938,6 +2939,9 @@ defmodule AshPostgres.MigrationGenerator do default in @uuid_functions -> ~S[fragment("gen_random_uuid()")] + default == (&Ash.UUIDv7.generate/0) -> + ~S[fragment("uuid_generate_v7()")] + default == (&DateTime.utc_now/0) -> ~S[fragment("(now() AT TIME ZONE 'utc')")] diff --git a/priv/resource_snapshots/test_repo/extensions.json b/priv/resource_snapshots/test_repo/extensions.json index e084bbff..08191fb2 100644 --- a/priv/resource_snapshots/test_repo/extensions.json +++ b/priv/resource_snapshots/test_repo/extensions.json @@ -6,5 +6,5 @@ "citext", "demo-functions_v1" ], - "ash_functions_version": 3 + "ash_functions_version": 4 } \ No newline at end of file diff --git a/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs b/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs new file mode 100644 index 00000000..17c00fcc --- /dev/null +++ b/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs @@ -0,0 +1,54 @@ +defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension420240622192713 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION uuid_generate_v7() + RETURNS UUID + AS $$ + DECLARE + timestamp TIMESTAMPTZ; + microseconds INT; + BEGIN + timestamp = clock_timestamp(); + microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; + + RETURN encode( + set_byte( + set_byte( + overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 + ), + 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int + ), + 7, microseconds::bit(8)::int + ), + 'hex')::UUID; + END + $$ + LANGUAGE PLPGSQL + VOLATILE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) + RETURNS TIMESTAMP WITHOUT TIME ZONE + AS $$ + SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); + $$ + LANGUAGE SQL + IMMUTABLE PARALLEL SAFE STRICT LEAKPROOF; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute("DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)") + end +end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 263c826d..de8e12ae 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -97,6 +97,7 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) + uuid_v7_primary_key(:other_id) attribute(:title, :string, public?: true) attribute(:second_title, :string, public?: true) attribute(:title_with_source, :string, source: :t_w_s, public?: true) @@ -141,6 +142,10 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file_contents =~ ~S[add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true] + # the migration adds the other_id, with its default + assert file_contents =~ + ~S[add :other_id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true] + # the migration adds the id, with its default assert file_contents =~ ~S[add :title_with_default, :text, default: "fred"] @@ -195,6 +200,7 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) + uuid_v7_primary_key(:other_id) attribute(:title, :string, public?: true) attribute(:second_title, :string, public?: true) end @@ -247,6 +253,10 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file_contents =~ ~S[add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true] + # the migration adds the other_id, with its default + assert file_contents =~ + ~S[add :other_id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true] + # the migration adds other attributes assert file_contents =~ ~S[add :title, :text] From cdbb5e294fafa6a1be7612e80a6408989fba3255 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 20:28:30 -0400 Subject: [PATCH 0548/1215] chore: ensure proper return value for installer --- lib/mix/tasks/ash_postgres.install.ex | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 364a315b..e3b4f1df 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -219,7 +219,6 @@ defmodule Mix.Tasks.AshPostgres.Install do |> remove_adapter_option() |> Sourceror.Zipper.top() |> configure_installed_extensions_function() - |> then(&{:ok, &1}) end ) end @@ -285,16 +284,17 @@ defmodule Mix.Tasks.AshPostgres.Install do end _ -> - Igniter.Code.Common.add_code(zipper, """ - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] - end - """) + {:ok, + Igniter.Code.Common.add_code(zipper, """ + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """)} end _ -> - zipper + {:ok, zipper} end end end From 5b9d0ea18ae19aa05ea17a433cae563eb9f2bbe8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 20:40:05 -0400 Subject: [PATCH 0549/1215] improvement: add DataCase creation for igniter installer (#346) * improvement: add DataCase creation for igniter installer * chore: remove kind favoring path --------- Co-authored-by: Igor Barakaiev --- lib/mix/tasks/ash_postgres.install.ex | 92 ++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index e3b4f1df..3c8f60bd 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -13,8 +13,9 @@ defmodule Mix.Tasks.AshPostgres.Install do |> setup_repo_module(otp_app, repo) |> configure_config(otp_app, repo) |> configure_dev(otp_app, repo) - |> configure_test(otp_app, repo) |> configure_runtime(otp_app, repo) + |> configure_test(otp_app, repo) + |> setup_data_case() |> Igniter.Project.Application.add_new_child(repo) |> Ash.Igniter.codegen("initialize") end @@ -193,6 +194,95 @@ defmodule Mix.Tasks.AshPostgres.Install do Ecto.Adapters.SQL.Sandbox ) |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) + |> Igniter.Project.Config.configure_new("test.exs", :ash, :disable_async?, true) + |> Igniter.Project.Config.configure_new("test.exs", :logger, :level, :warning) + end + + defp setup_data_case(igniter) do + default_data_case_contents = """ + @moduledoc \"\"\" + This module defines the setup for tests requiring + access to the application's data layer. + + You may define functions here to be used as helpers in + your tests. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use AshHq.DataCase, async: true`, although + this option is not recommended for other databases. + \"\"\" + + use ExUnit.CaseTemplate + + using do + quote do + alias #{Igniter.Code.Module.module_name_prefix()}.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import #{Igniter.Code.Module.module_name_prefix()}.DataCase + end + end + + setup tags do + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{Igniter.Code.Module.module_name_prefix()}.Repo, shared: not tags[:async]) + on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) + :ok + end + """ + + module_name = Igniter.Code.Module.module_name("DataCase") + + igniter + |> Igniter.Code.Module.find_and_update_or_create_module( + module_name, + default_data_case_contents, + # do nothing if already exists + fn zipper -> {:ok, zipper} end, + path: Igniter.Code.Module.proper_location(module_name, "test/support") + ) + end + + defp add_test_supports_to_elixirc_paths(igniter) do + Igniter.update_elixir_file(igniter, "mix.exs", fn zipper -> + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project), + {:ok, zipper} <- Igniter.Code.Module.move_to_def(zipper, :project, 0), + {:ok, zipper} <- + Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do + case Igniter.Code.List.move_to_list_item( + zipper, + &Kernel.match?({:elixirc_paths, _}, &1) + ) do + {:ok, zipper} -> + Sourceror.Zipper.top(zipper) + + _ -> + with {:ok, zipper} <- + Igniter.Code.List.append_to_list( + zipper, + quote(do: {:elixirc_paths, elixirc_paths(Mix.env())}) + ), + zipper <- Sourceror.Zipper.top(zipper), + {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper), + zipper <- + Igniter.Code.Common.add_code( + zipper, + "defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]" + ), + zipper <- + Igniter.Code.Common.add_code( + zipper, + "defp elixirc_paths(_), do: [\"lib\"]" + ) do + zipper + end + end + end + end) end defp setup_repo_module(igniter, otp_app, repo) do From ca27613f9f0efcb069bb3151cbf14fa9559e26b7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 20:42:04 -0400 Subject: [PATCH 0550/1215] chore: remove unused function --- lib/mix/tasks/ash_postgres.install.ex | 38 --------------------------- 1 file changed, 38 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 3c8f60bd..d7f6ed0c 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -247,44 +247,6 @@ defmodule Mix.Tasks.AshPostgres.Install do ) end - defp add_test_supports_to_elixirc_paths(igniter) do - Igniter.update_elixir_file(igniter, "mix.exs", fn zipper -> - with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project), - {:ok, zipper} <- Igniter.Code.Module.move_to_def(zipper, :project, 0), - {:ok, zipper} <- - Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do - case Igniter.Code.List.move_to_list_item( - zipper, - &Kernel.match?({:elixirc_paths, _}, &1) - ) do - {:ok, zipper} -> - Sourceror.Zipper.top(zipper) - - _ -> - with {:ok, zipper} <- - Igniter.Code.List.append_to_list( - zipper, - quote(do: {:elixirc_paths, elixirc_paths(Mix.env())}) - ), - zipper <- Sourceror.Zipper.top(zipper), - {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper), - zipper <- - Igniter.Code.Common.add_code( - zipper, - "defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]" - ), - zipper <- - Igniter.Code.Common.add_code( - zipper, - "defp elixirc_paths(_), do: [\"lib\"]" - ) do - zipper - end - end - end - end) - end - defp setup_repo_module(igniter, otp_app, repo) do default_repo_contents = """ From a085303da8689f5469566753e2c52b5377306231 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 20:52:25 -0400 Subject: [PATCH 0551/1215] chore: fix build --- lib/migration_generator/ash_functions.ex | 9 ++------- lib/mix/tasks/ash_postgres.install.ex | 8 ++++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 04eefa24..db7716d6 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -164,13 +164,8 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid), ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" end - defp ash_raise_error(prefix? \\ true) do - prefix = - if prefix? do - "ash_error: " - else - "" - end + defp ash_raise_error() do + prefix = "ash_error: " """ execute(\"\"\" diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index d7f6ed0c..cd6ef93c 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -194,12 +194,12 @@ defmodule Mix.Tasks.AshPostgres.Install do Ecto.Adapters.SQL.Sandbox ) |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) - |> Igniter.Project.Config.configure_new("test.exs", :ash, :disable_async?, true) - |> Igniter.Project.Config.configure_new("test.exs", :logger, :level, :warning) + |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) + |> Igniter.Project.Config.configure_new("test.exs", :logger, [:level], :warning) end defp setup_data_case(igniter) do - default_data_case_contents = """ + default_data_case_contents = ~S| @moduledoc \"\"\" This module defines the setup for tests requiring access to the application's data layer. @@ -233,7 +233,7 @@ defmodule Mix.Tasks.AshPostgres.Install do on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) :ok end - """ + | module_name = Igniter.Code.Module.module_name("DataCase") From d8b4091c99f8fdfb2c75b46386099ec59e9c5fbf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 9 Jul 2024 20:53:31 -0400 Subject: [PATCH 0552/1215] chore: release version v2.1.0 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index add91b31..9244234b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.0](https://github.com/ash-project/ash_postgres/compare/v2.0.12...v2.1.0) (2024-07-10) + +### Features: + +- [AshPostgres.DataLayer] add `storage_types` configuration (#342) +- [generators] add `mix ash_postgres.install` (`mix igniter.install ash_postgres`) + +### Bug Fixes: + +- [AshPostgres.DataLayer] ensure that `from_many?` relationships in lateral join have a limit applied + +- [migration generator] properly delete args passed from migrate to ecto + +### Improvements: + +- [Ash.Type.UUIDv7] add support for `:uuid_v7` type (#333) + +- [migration generator] order keys in snapshot json (#339) + ## [v2.0.12](https://github.com/ash-project/ash_postgres/compare/v2.0.11...v2.0.12) (2024-06-20) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index d7ee138e..8e4a8d18 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.0.12" + @version "2.1.0" def project do [ From 63324c19e92709617eccc2b9b775bcb651665e28 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Jul 2024 08:47:06 -0400 Subject: [PATCH 0553/1215] fix: properly interpolate module names in installer --- lib/mix/tasks/ash_postgres.install.ex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index cd6ef93c..002303ee 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -199,8 +199,8 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp setup_data_case(igniter) do - default_data_case_contents = ~S| - @moduledoc \"\"\" + default_data_case_contents = ~s| + @moduledoc """ This module defines the setup for tests requiring access to the application's data layer. @@ -213,23 +213,23 @@ defmodule Mix.Tasks.AshPostgres.Install do PostgreSQL, you can even run database tests asynchronously by setting `use AshHq.DataCase, async: true`, although this option is not recommended for other databases. - \"\"\" + """ use ExUnit.CaseTemplate using do quote do - alias #{Igniter.Code.Module.module_name_prefix()}.Repo + alias #{inspect(Igniter.Code.Module.module_name(Repo))} import Ecto import Ecto.Changeset import Ecto.Query - import #{Igniter.Code.Module.module_name_prefix()}.DataCase + import #{inspect(Igniter.Code.Module.module_name(DataCase))} end end setup tags do - pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{Igniter.Code.Module.module_name_prefix()}.Repo, shared: not tags[:async]) + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Code.Module.module_name(Repo))}, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) :ok end From 2a33a8c28542937a002b26fd046240cdf215bc0b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 10 Jul 2024 08:47:31 -0400 Subject: [PATCH 0554/1215] chore: release version v2.1.1 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9244234b..f05fb603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.1](https://github.com/ash-project/ash_postgres/compare/v2.1.0...v2.1.1) (2024-07-10) + + + + +### Bug Fixes: + +* properly interpolate module names in installer + ## [v2.1.0](https://github.com/ash-project/ash_postgres/compare/v2.0.12...v2.1.0) (2024-07-10) ### Features: diff --git a/mix.exs b/mix.exs index 8e4a8d18..d187fc18 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.0" + @version "2.1.1" def project do [ From 7dcf19e8952770d9c05da730524b0d577bccac5d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 11 Jul 2024 08:58:13 -0400 Subject: [PATCH 0555/1215] fix: update ash_sql to fix `select_merge` behavior --- lib/data_layer.ex | 20 -------------------- mix.exs | 2 +- mix.lock | 8 ++++---- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d218f9cf..1e8c26c1 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1442,16 +1442,6 @@ defmodule AshPostgres.DataLayer do query.limit || query.offset -> with {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do - select = - Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) - - root_query = - Ecto.Query.select_merge( - root_query, - [record], - map(record, ^select) - ) - {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} end @@ -1459,16 +1449,6 @@ defmodule AshPostgres.DataLayer do with root_query <- Ecto.Query.exclude(root_query, :order_by), {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do - select = - Enum.map(Ash.Resource.Info.attributes(resource), & &1.name) - - root_query = - Ecto.Query.select_merge( - root_query, - [record], - map(record, ^select) - ) - {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} end diff --git a/mix.exs b/mix.exs index d187fc18..81c773dc 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.8")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.11")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.9"}, {:ecto, "~> 3.9"}, diff --git a/mix.lock b/mix.lock index b4def659..8cc658e2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.1.0", "ad8ccd2271a0802b1a657c3558b4ace005ace46ddb3cf262713c891e5b0bdd6b", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.7", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "04b311a93127dc2ac84aeca0ff45c43236e01dfe120ede2c15d5827eef2194c0"}, - "ash_sql": {:hex, :ash_sql, "0.2.8", "ad20dc5487b68a095a12f84918d8ffab4bef12de59b7c5cf962a7c1dc03f5d81", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d120568a19f3d72a634c3bfa9ce5fa0fae1d3c44ee4cdb8c78f151e490bf849b"}, + "ash": {:hex, :ash, "3.1.2", "be3d955dc59c3d8314d0768cb9652ccb353f3ee1b7f48390220bddbb980065a6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9109e31583112ec3869a248d79796034c65ab332726d0c0025fe7f2200dd373"}, + "ash_sql": {:hex, :ash_sql, "0.2.11", "93d075524b443475f74d46dadd3a66839874860947da0650ecc91f358c59487b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1dbb42da5d2240bc7cf3c4a12d5ba2836914121856c17fd4c4e7eb16b7cecc7e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, - "igniter": {:hex, :igniter, "0.2.9", "7ef177063b417b6efbc74e57f05b5f3aad65b783f345df03462a2d5547d2bfa9", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "659e36a263325e7f87a8b1b09ef2387308a2ff12b917c27012cbaa0c46c957c9"}, + "igniter": {:hex, :igniter, "0.2.12", "e2e8fbb15effecb433f4096edbb0754282553544c75c3130d06ca09bdaa1fb13", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "51f3487a13441cd3e6e0d559689f8b0ba2c716834f86802e8a6760fdd1a2e579"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -36,7 +36,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, - "reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"}, + "reactor": {:hex, :reactor, "0.8.5", "7a621e0392a5975ed97938a4ddbbc92a6a31157fbd87446bc8bc6b1a0f49e56a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17b1976b9d333e55382dc108779078d5bbdbcd2c3d4033ea6dd52437339fe469"}, "req": {:hex, :req, "0.5.2", "70b4976e5fbefe84e5a57fd3eea49d4e9aa0ac015301275490eafeaec380f97f", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c63539ab4c2d6ced6114d2684276cef18ac185ee00674ee9af4b1febba1f986"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, From 8f77275be62f19e26e31e3d20f6b6e5df1f7a211 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 11 Jul 2024 12:02:31 -0400 Subject: [PATCH 0556/1215] chore: express dependencies on latest ecto/ecto_sql --- mix.exs | 4 ++-- mix.lock | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 81c773dc..50508e3c 100644 --- a/mix.exs +++ b/mix.exs @@ -165,8 +165,8 @@ defmodule AshPostgres.MixProject do {:ash, ash_version("~> 3.1")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.11")}, {:igniter, "~> 0.2.9"}, - {:ecto_sql, "~> 3.9"}, - {:ecto, "~> 3.9"}, + {:ecto_sql, "~> 3.11 and >= 3.11.3"}, + {:ecto, "~> 3.11 and >= 3.11.2"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies diff --git a/mix.lock b/mix.lock index 8cc658e2..fc465fb6 100644 --- a/mix.lock +++ b/mix.lock @@ -5,6 +5,7 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, From ef58c783137a55a675f152b2fa082bff0e3023e4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jul 2024 23:23:42 -0400 Subject: [PATCH 0557/1215] test: add more tests for atomic update interactions chore: get build passing --- lib/migration_generator/ash_functions.ex | 2 +- lib/mix/tasks/ash_postgres.install.ex | 6 +- mix.lock | 7 +- .../test_no_sandbox_repo/extensions.json | 4 +- .../test_repo/posts/20240712232026.json | 449 ++++++++++++++++++ ...2025_install_ash-functions_extension_4.exs | 54 +++ .../20240712232026_migrate_resources34.exs | 21 + test/atomics_test.exs | 28 ++ test/support/resources/post.ex | 11 + 9 files changed, 572 insertions(+), 10 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20240712232026.json create mode 100644 priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs create mode 100644 priv/test_repo/migrations/20240712232026_migrate_resources34.exs diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index db7716d6..609bb487 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -164,7 +164,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do "execute(\"DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid), ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])\")" end - defp ash_raise_error() do + defp ash_raise_error do prefix = "ash_error: " """ diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 002303ee..002822c6 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -219,17 +219,17 @@ defmodule Mix.Tasks.AshPostgres.Install do using do quote do - alias #{inspect(Igniter.Code.Module.module_name(Repo))} + alias #{inspect(Igniter.Code.Module.module_name("Repo"))} import Ecto import Ecto.Changeset import Ecto.Query - import #{inspect(Igniter.Code.Module.module_name(DataCase))} + import #{inspect(Igniter.Code.Module.module_name("DataCase"))} end end setup tags do - pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Code.Module.module_name(Repo))}, shared: not tags[:async]) + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Code.Module.module_name("Repo"))}, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) :ok end diff --git a/mix.lock b/mix.lock index fc465fb6..1d939f82 100644 --- a/mix.lock +++ b/mix.lock @@ -1,11 +1,10 @@ %{ - "ash": {:hex, :ash, "3.1.2", "be3d955dc59c3d8314d0768cb9652ccb353f3ee1b7f48390220bddbb980065a6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9109e31583112ec3869a248d79796034c65ab332726d0c0025fe7f2200dd373"}, - "ash_sql": {:hex, :ash_sql, "0.2.11", "93d075524b443475f74d46dadd3a66839874860947da0650ecc91f358c59487b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1dbb42da5d2240bc7cf3c4a12d5ba2836914121856c17fd4c4e7eb16b7cecc7e"}, + "ash": {:hex, :ash, "3.1.3", "c5c65e18107247df4857951fa546e720c6e2ef0afde07ad0c2f523725a751eb2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "530c04f32b2562352e48c92fab50bc837819c6cd3453c4fa9c9842b2e9d8483b"}, + "ash_sql": {:hex, :ash_sql, "0.2.13", "ac5ad6dad827253d156f0913ef005e279d817bf2d216d461fa8ffbff81c7eb34", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c493a05570873133896412fff43db6ed21f27527907cf6bbd19b380692e6fa9e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, - "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, @@ -14,7 +13,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, diff --git a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json index e084bbff..40d20b77 100644 --- a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json +++ b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json @@ -1,10 +1,10 @@ { + "ash_functions_version": 4, "installed": [ "ash-functions", "uuid-ossp", "pg_trgm", "citext", "demo-functions_v1" - ], - "ash_functions_version": 3 + ] } \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20240712232026.json b/priv/resource_snapshots/test_repo/posts/20240712232026.json new file mode 100644 index 00000000..60d6a6d1 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240712232026.json @@ -0,0 +1,449 @@ +{ + "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": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "D85FE48F112749094A13964D840D9B5ECAC765A2BDCBDCC38FB365FC852E87DE", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} diff --git a/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs b/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs new file mode 100644 index 00000000..97101dca --- /dev/null +++ b/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs @@ -0,0 +1,54 @@ +defmodule AshPostgres.TestNoSandboxRepo.Migrations.InstallAshFunctionsExtension420240712232023 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION uuid_generate_v7() + RETURNS UUID + AS $$ + DECLARE + timestamp TIMESTAMPTZ; + microseconds INT; + BEGIN + timestamp = clock_timestamp(); + microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; + + RETURN encode( + set_byte( + set_byte( + overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 + ), + 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int + ), + 7, microseconds::bit(8)::int + ), + 'hex')::UUID; + END + $$ + LANGUAGE PLPGSQL + VOLATILE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) + RETURNS TIMESTAMP WITHOUT TIME ZONE + AS $$ + SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); + $$ + LANGUAGE SQL + IMMUTABLE PARALLEL SAFE STRICT LEAKPROOF; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute("DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)") + end +end diff --git a/priv/test_repo/migrations/20240712232026_migrate_resources34.exs b/priv/test_repo/migrations/20240712232026_migrate_resources34.exs new file mode 100644 index 00000000..af406cf3 --- /dev/null +++ b/priv/test_repo/migrations/20240712232026_migrate_resources34.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources34 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(:posts) do + add(:constrained_int, :bigint) + end + end + + def down do + alter table(:posts) do + remove(:constrained_int) + end + end +end diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 573b7f21..9bfc8b88 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -125,6 +125,34 @@ defmodule AshPostgres.AtomicsTest do |> Ash.bulk_update!(:change_title_to_foo_unless_its_already_foo, %{}) end + test "an atomic validation can refer to an attribute being cast atomically" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "bar"}) + |> Ash.create!() + + # just asserting that there is no exception here + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.limit(1) + |> Ash.bulk_update!(:update_constrained_int, %{amount: 4}) + end + + test "an atomic validation can refer to an attribute being cast atomically, and will raise an error" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "bar"}) + |> Ash.create!() + + # just asserting that there is no exception here + assert_raise Ash.Error.Invalid, ~r/must be less than or equal to 10/, fn -> + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.limit(1) + |> Ash.bulk_update!(:update_constrained_int, %{amount: 12}) + end + end + test "an atomic works with a datetime" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e9f46e52..980470a1 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -212,6 +212,15 @@ defmodule AshPostgres.Test.Post do require_atomic?(false) manual(AshPostgres.Test.Post.ManualUpdate) end + + update :update_constrained_int do + argument(:amount, :integer, allow_nil?: false) + change(atomic_update(:constrained_int, expr((constrained_int || 0) + ^arg(:amount)))) + + validate(compare(:constrained_int, greater_than_or_equal_to: 2), + message: "You cannot select less than two." + ) + end end identities do @@ -246,6 +255,8 @@ defmodule AshPostgres.Test.Post do public?: true ) + attribute(:constrained_int, :integer, constraints: [min: 1, max: 10]) + attribute(:point, AshPostgres.Test.Point, public?: true) attribute(:composite_point, AshPostgres.Test.CompositePoint, public?: true) attribute(:stuff, :map, public?: true) From 00a9db3cc072bbce00d5e7472e653a676fc9717a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 13 Jul 2024 15:11:38 -0400 Subject: [PATCH 0558/1215] improvement: detect more cases when detecting types in expressions --- lib/sql_implementation.ex | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 836cc93b..a3fbc8b3 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -450,6 +450,15 @@ defmodule AshPostgres.SqlImplementation do :error end + %{__predicate__?: true} -> + {:ok, {:boolean, []}} + + %Ash.Query.BooleanExpression{} -> + {:ok, {:boolean, []}} + + %Ash.Query.Exists{} -> + {:ok, {:boolean, []}} + _ -> :error end From 27f63d8d8e2db1c3b3ca084f9606e7964c346bdb Mon Sep 17 00:00:00 2001 From: Robert Timis <65460527+TimisRobert@users.noreply.github.com> Date: Sat, 13 Jul 2024 22:06:52 +0200 Subject: [PATCH 0559/1215] error reproduction test (#349) --- config/config.exs | 4 +- .../test_repo/items/20240713134055.json | 49 +++++++ .../test_repo/other_items/20240713134055.json | 78 +++++++++++ .../test_repo/sub_items/20240713134055.json | 88 ++++++++++++ ...240713134055_multi_domain_calculations.exs | 127 ++++++++++++++++++ test/multi_domain_calculations_test.exs | 28 ++++ .../multi_domain_calculations/domain_one.ex | 8 ++ .../domain_one/item.ex | 39 ++++++ .../multi_domain_calculations/domain_two.ex | 9 ++ .../domain_two/other_item.ex | 51 +++++++ .../domain_two/sub_item.ex | 44 ++++++ 11 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/items/20240713134055.json create mode 100644 priv/resource_snapshots/test_repo/other_items/20240713134055.json create mode 100644 priv/resource_snapshots/test_repo/sub_items/20240713134055.json create mode 100644 priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs create mode 100644 test/multi_domain_calculations_test.exs create mode 100644 test/support/multi_domain_calculations/domain_one.ex create mode 100644 test/support/multi_domain_calculations/domain_one/item.ex create mode 100644 test/support/multi_domain_calculations/domain_two.ex create mode 100644 test/support/multi_domain_calculations/domain_two/other_item.ex create mode 100644 test/support/multi_domain_calculations/domain_two/sub_item.ex diff --git a/config/config.exs b/config/config.exs index 2b5d4262..4fc036c7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -52,7 +52,9 @@ if Mix.env() == :test do ash_domains: [ AshPostgres.Test.Domain, AshPostgres.MultitenancyTest.Domain, - AshPostgres.Test.ComplexCalculations.Domain + AshPostgres.Test.ComplexCalculations.Domain, + AshPostgres.Test.MultiDomainCalculations.DomainOne, + AshPostgres.Test.MultiDomainCalculations.DomainTwo ] config :ash, :compatible_foreign_key_types, [ diff --git a/priv/resource_snapshots/test_repo/items/20240713134055.json b/priv/resource_snapshots/test_repo/items/20240713134055.json new file mode 100644 index 00000000..f5b6f4a4 --- /dev/null +++ b/priv/resource_snapshots/test_repo/items/20240713134055.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "ABF32E9B15960350C650AA0F324442FEE568688B2176BF9441D168A20241CB44", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/other_items/20240713134055.json b/priv/resource_snapshots/test_repo/other_items/20240713134055.json new file mode 100644 index 00000000..33c8a3db --- /dev/null +++ b/priv/resource_snapshots/test_repo/other_items/20240713134055.json @@ -0,0 +1,78 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "other_items_item_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "items" + }, + "size": null, + "source": "item_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "8B0ECD0F08C43760F2F875336943046971139E77662FCAF01D5E9BA0B15009B7", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "other_items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/sub_items/20240713134055.json b/priv/resource_snapshots/test_repo/sub_items/20240713134055.json new file mode 100644 index 00000000..f4ab908f --- /dev/null +++ b/priv/resource_snapshots/test_repo/sub_items/20240713134055.json @@ -0,0 +1,88 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "amount", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "sub_items_other_item_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "other_items" + }, + "size": null, + "source": "other_item_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "977777A71F41127683F7EB559EB79213EE7516FA45B94D489C8869DD200F02F5", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "sub_items" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs b/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs new file mode 100644 index 00000000..04dce9a0 --- /dev/null +++ b/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs @@ -0,0 +1,127 @@ +defmodule AshPostgres.TestRepo.Migrations.MultiDomainCalculations 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 + create table(:sub_items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true) + add(:amount, :bigint, null: false) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:other_item_id, :uuid, null: false) + end + + create table(:other_items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true) + end + + alter table(:sub_items) do + modify( + :other_item_id, + references(:other_items, + column: :id, + name: "sub_items_other_item_id_fkey", + type: :uuid, + prefix: "public", + on_delete: :delete_all + ) + ) + end + + create(index(:sub_items, [:other_item_id])) + + alter table(:other_items) do + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:item_id, :uuid, null: false) + end + + create table(:items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true) + end + + alter table(:other_items) do + modify( + :item_id, + references(:items, + column: :id, + name: "other_items_item_id_fkey", + type: :uuid, + prefix: "public", + on_delete: :delete_all + ) + ) + end + + create(index(:other_items, [:item_id])) + + alter table(:items) do + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + end + + def down do + alter table(:items) do + remove(:updated_at) + remove(:inserted_at) + end + + drop_if_exists(index(:other_items, [:item_id])) + + drop(constraint(:other_items, "other_items_item_id_fkey")) + + alter table(:other_items) do + modify(:item_id, :uuid) + end + + drop(table(:items)) + + alter table(:other_items) do + remove(:item_id) + remove(:updated_at) + remove(:inserted_at) + end + + drop_if_exists(index(:sub_items, [:other_item_id])) + + drop(constraint(:sub_items, "sub_items_other_item_id_fkey")) + + alter table(:sub_items) do + modify(:other_item_id, :uuid) + end + + drop(table(:other_items)) + + drop(table(:sub_items)) + end +end diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs new file mode 100644 index 00000000..ba49aeb2 --- /dev/null +++ b/test/multi_domain_calculations_test.exs @@ -0,0 +1,28 @@ +defmodule AshPostgres.Test.MultiDomainCalculationsTest do + use AshPostgres.RepoCase, async: false + + require Ash.Query + + test "total is returned correctly" do + item = + AshPostgres.Test.MultiDomainCalculations.DomainOne.Item + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + other_item = + AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem + |> Ash.Changeset.for_create(:create, %{item_id: item.id}) + |> Ash.create!() + + for i <- 0..2 do + AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem + |> Ash.Changeset.for_create(:create, %{other_item_id: other_item.id, amount: i}) + |> Ash.create!() + end + + assert %{total_amount: 3} = + Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, + load: [:total_amount] + ) + end +end diff --git a/test/support/multi_domain_calculations/domain_one.ex b/test/support/multi_domain_calculations/domain_one.ex new file mode 100644 index 00000000..ff4e072f --- /dev/null +++ b/test/support/multi_domain_calculations/domain_one.ex @@ -0,0 +1,8 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne do + @moduledoc false + use Ash.Domain + + resources do + resource(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item) + end +end diff --git a/test/support/multi_domain_calculations/domain_one/item.ex b/test/support/multi_domain_calculations/domain_one/item.ex new file mode 100644 index 00000000..5d76e9eb --- /dev/null +++ b/test/support/multi_domain_calculations/domain_one/item.ex @@ -0,0 +1,39 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne.Item do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer], + domain: AshPostgres.Test.MultiDomainCalculations.DomainOne + + alias AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem + + attributes do + uuid_v7_primary_key(:id) + create_timestamp(:inserted_at) + update_timestamp(:updated_at) + end + + relationships do + has_one(:other_item, OtherItem) + end + + actions do + defaults([:read, :destroy, update: :*, create: :*]) + end + + calculations do + calculate(:total_amount, :integer, expr(other_item.total_amount)) + end + + policies do + policy always() do + authorize_if(always()) + end + end + + postgres do + table "items" + repo(AshPostgres.TestRepo) + end +end diff --git a/test/support/multi_domain_calculations/domain_two.ex b/test/support/multi_domain_calculations/domain_two.ex new file mode 100644 index 00000000..f9ccde38 --- /dev/null +++ b/test/support/multi_domain_calculations/domain_two.ex @@ -0,0 +1,9 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo do + @moduledoc false + use Ash.Domain + + resources do + resource(AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem) + resource(AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem) + end +end diff --git a/test/support/multi_domain_calculations/domain_two/other_item.ex b/test/support/multi_domain_calculations/domain_two/other_item.ex new file mode 100644 index 00000000..60cdea7a --- /dev/null +++ b/test/support/multi_domain_calculations/domain_two/other_item.ex @@ -0,0 +1,51 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer], + domain: AshPostgres.Test.MultiDomainCalculations.DomainTwo + + alias AshPostgres.Test.MultiDomainCalculations.DomainOne.Item + alias AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem + + attributes do + uuid_v7_primary_key(:id) + create_timestamp(:inserted_at) + update_timestamp(:updated_at) + end + + relationships do + belongs_to(:item, Item, allow_nil?: false) + has_many(:sub_items, SubItem) + end + + actions do + defaults([:read, :destroy, create: [:*, :item_id], update: :*]) + end + + aggregates do + sum :total_sub_items_amount, :sub_items, :total_amount do + default(0) + end + end + + calculations do + calculate(:total_amount, :integer, expr(total_sub_items_amount)) + end + + policies do + policy always() do + authorize_if(always()) + end + end + + postgres do + table "other_items" + repo(AshPostgres.TestRepo) + + references do + reference :item, on_delete: :delete, index?: true + end + end +end diff --git a/test/support/multi_domain_calculations/domain_two/sub_item.ex b/test/support/multi_domain_calculations/domain_two/sub_item.ex new file mode 100644 index 00000000..c5411e51 --- /dev/null +++ b/test/support/multi_domain_calculations/domain_two/sub_item.ex @@ -0,0 +1,44 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer], + domain: AshPostgres.Test.MultiDomainCalculations.DomainTwo + + alias AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem + + attributes do + uuid_v7_primary_key(:id) + attribute(:amount, :integer, allow_nil?: false) + create_timestamp(:inserted_at) + update_timestamp(:updated_at) + end + + relationships do + belongs_to(:other_item, OtherItem, allow_nil?: false) + end + + actions do + defaults([:read, :destroy, create: [:*, :other_item_id, :amount], update: :*]) + end + + calculations do + calculate(:total_amount, :integer, expr(amount)) + end + + policies do + policy always() do + authorize_if(always()) + end + end + + postgres do + table "sub_items" + repo(AshPostgres.TestRepo) + + references do + reference :other_item, on_delete: :delete, index?: true + end + end +end From 8aaab0b6d4ab6ae3ce444c4d9b526c508a3d068d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 13 Jul 2024 16:15:03 -0400 Subject: [PATCH 0560/1215] chore: update ash dependency --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 50508e3c..585871c1 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.1")}, + {:ash, ash_version("~> 3.1 and >= 3.1.4")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.11")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, From 1cabf102be4e70262b4fba49b98f469eb05c1c83 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 13 Jul 2024 16:15:55 -0400 Subject: [PATCH 0561/1215] chore: release version v2.1.2 --- CHANGELOG.md | 5 +++++ mix.exs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f05fb603..b80cec42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.2](https://github.com/ash-project/ash_postgres/compare/v2.1.1...v2.1.2) (2024-07-13) + + + + ## [v2.1.1](https://github.com/ash-project/ash_postgres/compare/v2.1.0...v2.1.1) (2024-07-10) diff --git a/mix.exs b/mix.exs index 585871c1..b5ecef0f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.1" + @version "2.1.2" def project do [ From 0803c65694961faea82ed7cc2a3f98e17f10aec2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 13 Jul 2024 16:16:44 -0400 Subject: [PATCH 0562/1215] chore: update changelog --- CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b80cec42..35e5bdf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,13 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.1.2](https://github.com/ash-project/ash_postgres/compare/v2.1.1...v2.1.2) (2024-07-13) - - +- [query builder] update ash & improve type casting behavior ## [v2.1.1](https://github.com/ash-project/ash_postgres/compare/v2.1.0...v2.1.1) (2024-07-10) - - - ### Bug Fixes: -* properly interpolate module names in installer +- [mix ash_postgres.install] properly interpolate module names in installer ## [v2.1.0](https://github.com/ash-project/ash_postgres/compare/v2.0.12...v2.1.0) (2024-07-10) From ca02cef7fc17e324b55fb34c64bda22e5f1b417b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 13 Jul 2024 16:17:01 -0400 Subject: [PATCH 0563/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 1d939f82..0be76904 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.1.3", "c5c65e18107247df4857951fa546e720c6e2ef0afde07ad0c2f523725a751eb2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "530c04f32b2562352e48c92fab50bc837819c6cd3453c4fa9c9842b2e9d8483b"}, + "ash": {:hex, :ash, "3.1.4", "5e77bd66d3008b9d03ce1d172cebe6a7a916ea61a715b1a84421819c70cda85a", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c5e1b777fdd2ee488c60ea973aa9e1d0da255d2bd362a547482c047f9f0c362"}, "ash_sql": {:hex, :ash_sql, "0.2.13", "ac5ad6dad827253d156f0913ef005e279d817bf2d216d461fa8ffbff81c7eb34", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c493a05570873133896412fff43db6ed21f27527907cf6bbd19b380692e6fa9e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From cef4c8fce033868f82abef90138181fb3c612f00 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 09:25:49 -0400 Subject: [PATCH 0564/1215] improvement: support new type determination code --- lib/data_layer.ex | 1 + lib/sql_implementation.ex | 250 ++------------------------------- mix.exs | 2 +- test/atomics_test.exs | 7 + test/support/resources/post.ex | 8 +- 5 files changed, 28 insertions(+), 240 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1e8c26c1..69322681 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1,5 +1,6 @@ defmodule AshPostgres.DataLayer do require Ecto.Query + require Ash.Expr @manage_tenant %Spark.Dsl.Section{ name: :manage_tenant, diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index a3fbc8b3..c0530bd4 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -228,247 +228,21 @@ defmodule AshPostgres.SqlImplementation do end @impl true - def determine_types(mod, values) do - Code.ensure_compiled(mod) + def determine_types(mod, args) do + {types, returns} = Ash.Expr.determine_types(mod, args) - name = - cond do - function_exported?(mod, :operator, 0) -> - mod.operator() - - function_exported?(mod, :name, 0) -> - mod.name() - - true -> - nil + returns = + case returns do + {type, constraints} -> parameterized_type(type, constraints) + other -> other end - cond do - :erlang.function_exported(mod, :types, 0) -> - mod.types() - - :erlang.function_exported(mod, :args, 0) -> - mod.args() - - true -> - [:any] - end - |> then(fn types -> - Enum.concat(Map.keys(Ash.Query.Operator.operator_overloads(name) || %{}), types) - end) - |> Enum.reject(&(&1 == :any)) - |> Enum.filter(fn typeset -> - typeset == :same || - length(typeset) == length(values) - end) - |> Enum.find_value(Enum.map(values, fn _ -> nil end), fn typeset -> - types_and_values = - if typeset == :same do - Enum.map(values, &{:same, &1}) - else - Enum.zip(typeset, values) - end - - types_and_values - |> Enum.with_index() - |> Enum.reduce_while(%{must_adopt_basis: [], basis: nil, types: [], fallback_basis: nil}, fn - {{vague_type, value}, index}, acc when vague_type in [:any, :same] -> - case determine_type(value) do - {:ok, {type, constraints}} -> - case acc[:basis] do - nil -> - if vague_type == :any do - acc = Map.update!(acc, :types, &[{type, constraints} | &1]) - {:cont, Map.put(acc, :basis, {type, constraints})} - else - acc = - acc - |> Map.update!(:types, &[nil | &1]) - |> Map.put(:fallback_basis, {type, constraints}) - - {:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])} - end - - {^type, matched_constraints} -> - {:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])} - - _ -> - {:halt, :error} - end - - :error -> - acc = Map.update!(acc, :types, &[nil | &1]) - {:cont, Map.update!(acc, :must_adopt_basis, &[{index, fn x -> x end} | &1])} - end - - {{{:array, vague_type}, value}, index}, acc when vague_type in [:any, :same] -> - case determine_type(value) do - {:ok, {{:array, type}, constraints}} -> - case acc[:basis] do - nil -> - if vague_type == :any do - acc = Map.update!(acc, :types, &[{:array, {type, constraints}} | &1]) - {:cont, Map.put(acc, :basis, {type, constraints})} - else - acc = - acc - |> Map.update!(:types, &[nil | &1]) - |> Map.put(:fallback_basis, {type, constraints}) - - {:cont, - Map.update!( - acc, - :must_adopt_basis, - &[ - {index, - fn {type, constraints} -> {{:array, type}, items: constraints} end} - | &1 - ] - )} - end - - {^type, matched_constraints} -> - {:cont, Map.update!(acc, :types, &[{:array, {type, matched_constraints}} | &1])} - - _ -> - {:halt, :error} - end - - _ -> - acc = Map.update!(acc, :types, &[nil | &1]) - - {:cont, - Map.update!( - acc, - :must_adopt_basis, - &[ - {index, fn {type, constraints} -> {{:array, type}, items: constraints} end} - | &1 - ] - )} - end - - {{{type, constraints}, value}, _index}, acc -> - cond do - !Ash.Expr.expr?(value) && !matches_type?(type, value, constraints) -> - {:halt, :error} - - Ash.Expr.expr?(value) -> - case determine_type(value) do - {:ok, {^type, matched_constraints}} -> - {:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])} - - _ -> - {:halt, :error} - end - - true -> - {:cont, Map.update!(acc, :types, &[{type, constraints} | &1])} - end - - {{type, value}, _index}, acc -> - cond do - !Ash.Expr.expr?(value) && !matches_type?(type, value, []) -> - {:halt, :error} - - Ash.Expr.expr?(value) -> - case determine_type(value) do - {:ok, {^type, matched_constraints}} -> - {:cont, Map.update!(acc, :types, &[{type, matched_constraints} | &1])} - - _ -> - {:halt, :error} - end - - true -> - {:cont, Map.update!(acc, :types, &[{type, []} | &1])} - end - end) - |> then(fn - %{basis: nil, fallback_basis: fallback_basis} = data when not is_nil(fallback_basis) -> - %{data | basis: fallback_basis} - - data -> - data - end) - |> case do - :error -> - nil - - %{basis: nil, must_adopt_basis: [], types: types} -> - types - |> Enum.reverse() - |> Enum.map(fn {type, constraints} -> - parameterized_type(type, constraints) - end) - - %{basis: nil, must_adopt_basis: _} -> - nil - - %{basis: basis, must_adopt_basis: basis_adopters, types: types} -> - basis_adopters - |> Enum.reduce( - Enum.reverse(types), - fn {index, function_of_basis}, types -> - List.replace_at(types, index, function_of_basis.(basis)) - end - ) - |> Enum.map(fn {type, constraints} -> - parameterized_type(type, constraints) - end) - end - end) - end - - defp determine_type(value) do - case value do - %Ash.Query.Function.Type{arguments: [_, type, constraints]} -> - if Ash.Type.ash_type?(type) do - {:ok, {type, constraints}} - else - :error - end - - %Ash.Query.Function.Type{arguments: [_, type]} -> - if Ash.Type.ash_type?(type) do - {:ok, {type, []}} - else - :error - end - - %Ash.Query.Ref{attribute: %{type: type, constraints: constraints}} -> - if Ash.Type.ash_type?(type) do - {:ok, {type, constraints}} - else - :error - end - - %Ash.Query.Ref{attribute: %{type: type}} -> - if Ash.Type.ash_type?(type) do - {:ok, {type, []}} - else - :error - end - - %{__predicate__?: true} -> - {:ok, {:boolean, []}} - - %Ash.Query.BooleanExpression{} -> - {:ok, {:boolean, []}} - - %Ash.Query.Exists{} -> - {:ok, {:boolean, []}} - - _ -> - :error - end - end - - defp matches_type?({:array, type}, %MapSet{} = value, constraints) do - Enum.all?(value, &matches_type?(&1, type, constraints[:items])) - end + {Enum.map(types, fn + {type, constraints} -> + parameterized_type(type, constraints) - defp matches_type?(type, value, constraints) do - Ash.Type.matches_type?(type, value, constraints) + other -> + other + end), returns} end end diff --git a/mix.exs b/mix.exs index b5ecef0f..e0d9a9b2 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.1 and >= 3.1.4")}, + {:ash, ash_version("~> 3.1 and >= 3.1.5")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.11")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 9bfc8b88..a0a34200 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -151,6 +151,13 @@ defmodule AshPostgres.AtomicsTest do |> Ash.Query.limit(1) |> Ash.bulk_update!(:update_constrained_int, %{amount: 12}) end + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "bar", constrained_int: 5}) + |> Ash.create!() + + assert %{constrained_int: 10} = Post.update_constrained_int!(post.id, 5) end test "an atomic works with a datetime" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 980470a1..dbcfe9f2 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -255,7 +255,12 @@ defmodule AshPostgres.Test.Post do public?: true ) - attribute(:constrained_int, :integer, constraints: [min: 1, max: 10]) + attribute(:constrained_int, :integer, + constraints: [min: 1, max: 10], + default: 2, + allow_nil?: false, + public?: true + ) attribute(:point, AshPostgres.Test.Point, public?: true) attribute(:composite_point, AshPostgres.Test.CompositePoint, public?: true) @@ -287,6 +292,7 @@ defmodule AshPostgres.Test.Post do define(:get_by_id, action: :read, get_by: [:id]) define(:increment_score, args: [{:optional, :amount}]) define(:destroy) + define(:update_constrained_int, args: [:amount]) end relationships do From f55f146bd7bafb576e43458b46a8d8037aa369fc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 09:26:37 -0400 Subject: [PATCH 0565/1215] chore: release version v2.1.3 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e5bdf1..dbdc15bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.3](https://github.com/ash-project/ash_postgres/compare/v2.1.2...v2.1.3) (2024-07-14) + + + + +### Improvements: + +* support new type determination code + ## [v2.1.2](https://github.com/ash-project/ash_postgres/compare/v2.1.1...v2.1.2) (2024-07-13) - [query builder] update ash & improve type casting behavior diff --git a/mix.exs b/mix.exs index e0d9a9b2..a541428c 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.2" + @version "2.1.3" def project do [ From 6a4586795e7b6fd680743d9cf64e4f7e6341e1ec Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 09:27:01 -0400 Subject: [PATCH 0566/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 0be76904..a7f6af72 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.1.4", "5e77bd66d3008b9d03ce1d172cebe6a7a916ea61a715b1a84421819c70cda85a", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c5e1b777fdd2ee488c60ea973aa9e1d0da255d2bd362a547482c047f9f0c362"}, + "ash": {:hex, :ash, "3.1.5", "2e9b3c54ce5d52661e51ae76cf1648aa47a41876749025760267ca57e153c6ef", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9f43e066e0b478343091242c133d7a32b22f9f9be111d1bc950f76f1609c83e3"}, "ash_sql": {:hex, :ash_sql, "0.2.13", "ac5ad6dad827253d156f0913ef005e279d817bf2d216d461fa8ffbff81c7eb34", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c493a05570873133896412fff43db6ed21f27527907cf6bbd19b380692e6fa9e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From bfd3b64dd6b4f76326316e95989986ea9641d43b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 10:07:32 -0400 Subject: [PATCH 0567/1215] chore: update ash_sql --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index a541428c..ed09179c 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.1.5")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.11")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.16")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index a7f6af72..51fb1de5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.1.5", "2e9b3c54ce5d52661e51ae76cf1648aa47a41876749025760267ca57e153c6ef", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9f43e066e0b478343091242c133d7a32b22f9f9be111d1bc950f76f1609c83e3"}, - "ash_sql": {:hex, :ash_sql, "0.2.13", "ac5ad6dad827253d156f0913ef005e279d817bf2d216d461fa8ffbff81c7eb34", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c493a05570873133896412fff43db6ed21f27527907cf6bbd19b380692e6fa9e"}, + "ash_sql": {:hex, :ash_sql, "0.2.16", "48317c1d353f9c0578dbf9ae7cc913f6d409a8bcccff5c76a81daf2d6d883df6", [:mix], [{:ash, ">= 3.1.5 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7355a75bfa335bbaa73e4f7ba429fc487adfc178d054e7aa0af7ea1a57a35b4c"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, From da925604a8a467e2f42b2eb5f61754869e654d0f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 11:44:33 -0400 Subject: [PATCH 0568/1215] improvement: use latest type casting code from ash --- lib/sql_implementation.ex | 10 +++++----- mix.exs | 4 ++-- mix.lock | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index c0530bd4..aa0a7c7e 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -228,11 +228,11 @@ defmodule AshPostgres.SqlImplementation do end @impl true - def determine_types(mod, args) do - {types, returns} = Ash.Expr.determine_types(mod, args) + def determine_types(mod, args, returns \\ nil) do + {types, new_returns} = Ash.Expr.determine_types(mod, args, returns) - returns = - case returns do + new_returns = + case new_returns do {type, constraints} -> parameterized_type(type, constraints) other -> other end @@ -243,6 +243,6 @@ defmodule AshPostgres.SqlImplementation do other -> other - end), returns} + end), new_returns || returns} end end diff --git a/mix.exs b/mix.exs index ed09179c..59ea23c7 100644 --- a/mix.exs +++ b/mix.exs @@ -162,8 +162,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.1 and >= 3.1.5")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.16")}, + {:ash, ash_version("~> 3.1 and >= 3.1.7")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.17")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 51fb1de5..a18e9712 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.1.5", "2e9b3c54ce5d52661e51ae76cf1648aa47a41876749025760267ca57e153c6ef", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9f43e066e0b478343091242c133d7a32b22f9f9be111d1bc950f76f1609c83e3"}, - "ash_sql": {:hex, :ash_sql, "0.2.16", "48317c1d353f9c0578dbf9ae7cc913f6d409a8bcccff5c76a81daf2d6d883df6", [:mix], [{:ash, ">= 3.1.5 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7355a75bfa335bbaa73e4f7ba429fc487adfc178d054e7aa0af7ea1a57a35b4c"}, + "ash": {:hex, :ash, "3.1.7", "99e50347e70dd6fa62c9afa5246ef83e363bb7c0236b2081d7b7f35b6095c099", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c16b5a8c9dc5bd081b61e76e97abff6f61fbf7f0f34955901755644dcd7519ce"}, + "ash_sql": {:hex, :ash_sql, "0.2.17", "bc548b434ce9ca6f2bf85469ecef9353bfbf99fad11382fe8a2219e16feeb4ff", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e2ab3b6b545b20ec037f76af0ddc3a99f9da96f2f7d10f3c17c766b9fd7b6445"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, From 1c4a887ba99595dc7c9fa48d5dee716aaae4d839 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 11:45:10 -0400 Subject: [PATCH 0569/1215] chore: release version v2.1.4 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbdc15bb..221cd765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.4](https://github.com/ash-project/ash_postgres/compare/v2.1.3...v2.1.4) (2024-07-14) + + + + +### Improvements: + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.3](https://github.com/ash-project/ash_postgres/compare/v2.1.2...v2.1.3) (2024-07-14) diff --git a/mix.exs b/mix.exs index 59ea23c7..cc689cdd 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.3" + @version "2.1.4" def project do [ From 252acd423a0a0417ec67da32408d2ffc482e8cf0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 14 Jul 2024 22:11:06 -0400 Subject: [PATCH 0570/1215] chore: don't use deprecated function --- lib/mix/tasks/ash_postgres.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 002822c6..2231d4d8 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -325,7 +325,7 @@ defmodule Mix.Tasks.AshPostgres.Install do defp configure_installed_extensions_function(zipper) do case Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo) do {:ok, zipper} -> - case Igniter.Code.Module.move_to_def(zipper, :installed_extensions, 0) do + case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do {:ok, zipper} -> case Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do {:ok, zipper} -> From 5a9812f0164dd270d28129c72f6ec80bc7788955 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 09:20:39 -0400 Subject: [PATCH 0571/1215] fix: ensure synthesized query aggregates have context set --- lib/data_layer.ex | 4 ++-- mix.lock | 4 ++-- test/multi_domain_calculations_test.exs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 69322681..0ed9f72f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1268,8 +1268,8 @@ defmodule AshPostgres.DataLayer do end @impl true - def resource_to_query(resource, _) do - AshSql.Query.resource_to_query(resource, AshPostgres.SqlImplementation) + def resource_to_query(resource, domain) do + AshSql.Query.resource_to_query(resource, AshPostgres.SqlImplementation, domain) end @impl true diff --git a/mix.lock b/mix.lock index a18e9712..ecbe9eac 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.1.7", "99e50347e70dd6fa62c9afa5246ef83e363bb7c0236b2081d7b7f35b6095c099", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c16b5a8c9dc5bd081b61e76e97abff6f61fbf7f0f34955901755644dcd7519ce"}, + "ash": {:hex, :ash, "3.1.8", "427fa57ad03185cc3e4ea83179d8a07e8158578ce4048e7485a4f2f9375c4329", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9749695ce573efc3cc9f799697e3aeb3e69e9d4409e90aefc410d55309044182"}, "ash_sql": {:hex, :ash_sql, "0.2.17", "bc548b434ce9ca6f2bf85469ecef9353bfbf99fad11382fe8a2219e16feeb4ff", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e2ab3b6b545b20ec037f76af0ddc3a99f9da96f2f7d10f3c17c766b9fd7b6445"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, - "igniter": {:hex, :igniter, "0.2.12", "e2e8fbb15effecb433f4096edbb0754282553544c75c3130d06ca09bdaa1fb13", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "51f3487a13441cd3e6e0d559689f8b0ba2c716834f86802e8a6760fdd1a2e579"}, + "igniter": {:hex, :igniter, "0.2.13", "dbf743887f73de135f38a121e31dd16ce29ea355b1de85c876848fecf335ca0a", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f6e7e98ac9f42ca5fa6121d4212c6d462049e43fbf91ec56e3b2cf2895eba86b"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs index ba49aeb2..bf659855 100644 --- a/test/multi_domain_calculations_test.exs +++ b/test/multi_domain_calculations_test.exs @@ -20,7 +20,7 @@ defmodule AshPostgres.Test.MultiDomainCalculationsTest do |> Ash.create!() end - assert %{total_amount: 3} = + assert [%{total_amount: 3}] = Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, load: [:total_amount] ) From c2596a5d0f932f42edcf24fd56f80b9cb27bb69d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 09:24:59 -0400 Subject: [PATCH 0572/1215] chore: update ash_sql for fix --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index cc689cdd..954dcb4a 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.1.7")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.17")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.18")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index ecbe9eac..fb2234b8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.1.8", "427fa57ad03185cc3e4ea83179d8a07e8158578ce4048e7485a4f2f9375c4329", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9749695ce573efc3cc9f799697e3aeb3e69e9d4409e90aefc410d55309044182"}, - "ash_sql": {:hex, :ash_sql, "0.2.17", "bc548b434ce9ca6f2bf85469ecef9353bfbf99fad11382fe8a2219e16feeb4ff", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e2ab3b6b545b20ec037f76af0ddc3a99f9da96f2f7d10f3c17c766b9fd7b6445"}, + "ash_sql": {:hex, :ash_sql, "0.2.18", "2ad66b4cbad0009d78b801948a32507274412d85c76cd90b8d691d37149f07aa", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ea1849f397909373cd0b728677f1d5a1155ed7fe5f7e9db03cd16dd5a506d0d6"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, From c75875a556010ea5b8c1b980497262960fe37753 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 09:53:36 -0400 Subject: [PATCH 0573/1215] chore: clean mix.lock --- mix.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mix.lock b/mix.lock index fb2234b8..a6ad9f7e 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,6 @@ "ash_sql": {:hex, :ash_sql, "0.2.18", "2ad66b4cbad0009d78b801948a32507274412d85c76cd90b8d691d37149f07aa", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ea1849f397909373cd0b728677f1d5a1155ed7fe5f7e9db03cd16dd5a506d0d6"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, @@ -18,26 +17,20 @@ "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, - "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "igniter": {:hex, :igniter, "0.2.13", "dbf743887f73de135f38a121e31dd16ce29ea355b1de85c876848fecf335ca0a", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f6e7e98ac9f42ca5fa6121d4212c6d462049e43fbf91ec56e3b2cf2895eba86b"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.5", "7a621e0392a5975ed97938a4ddbbc92a6a31157fbd87446bc8bc6b1a0f49e56a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17b1976b9d333e55382dc108779078d5bbdbcd2c3d4033ea6dd52437339fe469"}, - "req": {:hex, :req, "0.5.2", "70b4976e5fbefe84e5a57fd3eea49d4e9aa0ac015301275490eafeaec380f97f", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c63539ab4c2d6ced6114d2684276cef18ac185ee00674ee9af4b1febba1f986"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, From 52f02506526b0379be60bbf63d81535a267ac188 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 09:54:09 -0400 Subject: [PATCH 0574/1215] chore: generate migrations --- .../test_repo/posts/20240715135403.json | 449 ++++++++++++++++++ .../20240715135403_migrate_resources35.exs | 21 + 2 files changed, 470 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20240715135403.json create mode 100644 priv/test_repo/migrations/20240715135403_migrate_resources35.exs diff --git a/priv/resource_snapshots/test_repo/posts/20240715135403.json b/priv/resource_snapshots/test_repo/posts/20240715135403.json new file mode 100644 index 00000000..2aca8e19 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240715135403.json @@ -0,0 +1,449 @@ +{ + "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": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "E051B6EFFFE6A769AF04B32B8E148FF2B763024D7781796A4ED44F27B7D15979", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240715135403_migrate_resources35.exs b/priv/test_repo/migrations/20240715135403_migrate_resources35.exs new file mode 100644 index 00000000..5819ea53 --- /dev/null +++ b/priv/test_repo/migrations/20240715135403_migrate_resources35.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources35 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(:posts) do + modify(:constrained_int, :bigint, null: false, default: 2) + end + end + + def down do + alter table(:posts) do + modify(:constrained_int, :bigint, null: true, default: nil) + end + end +end From 6f7d163da8cf386000eee8d3eb112e62a978c000 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 10:00:47 -0400 Subject: [PATCH 0575/1215] chore: update typespecs --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index a6ad9f7e..ca537d5f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.1.8", "427fa57ad03185cc3e4ea83179d8a07e8158578ce4048e7485a4f2f9375c4329", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9749695ce573efc3cc9f799697e3aeb3e69e9d4409e90aefc410d55309044182"}, - "ash_sql": {:hex, :ash_sql, "0.2.18", "2ad66b4cbad0009d78b801948a32507274412d85c76cd90b8d691d37149f07aa", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ea1849f397909373cd0b728677f1d5a1155ed7fe5f7e9db03cd16dd5a506d0d6"}, + "ash_sql": {:hex, :ash_sql, "0.2.18", "e88b316ae6b820e0b19ddb4487662b24ef661a48de3ae836dda3d18f6de66556", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b43c7dfc1136377d55f9082d752503ff1afadccc3aa4d4abde7c09be5e025ef2"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From ab97b2947b4a5a5fff5017694e6e3424b85f6031 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 10:25:39 -0400 Subject: [PATCH 0576/1215] improvement: add `binding()` expression --- lib/data_layer.ex | 3 ++- lib/functions/binding.ex | 9 +++++++++ lib/sql_implementation.ex | 15 +++++++++++++++ test/calculation_test.exs | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 lib/functions/binding.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 0ed9f72f..450acc8d 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -785,7 +785,8 @@ defmodule AshPostgres.DataLayer do functions = [ AshPostgres.Functions.Like, - AshPostgres.Functions.ILike + AshPostgres.Functions.ILike, + AshPostgres.Functions.Binding ] functions = diff --git a/lib/functions/binding.ex b/lib/functions/binding.ex new file mode 100644 index 00000000..92fc9d6f --- /dev/null +++ b/lib/functions/binding.ex @@ -0,0 +1,9 @@ +defmodule AshPostgres.Functions.Binding do + @moduledoc """ + Refers to the current table binding. + """ + + use Ash.Query.Function, name: :binding + + def args, do: [[]] +end diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index aa0a7c7e..90414cf3 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -41,6 +41,21 @@ defmodule AshPostgres.SqlImplementation do {:ok, Ecto.Query.dynamic(fragment("'[]'::jsonb")), acc} end + def expr(query, %AshPostgres.Functions.Binding{}, _bindings, _embedded?, acc, _type) do + binding = AshSql.Bindings.get_binding( + query.__ash_bindings__.resource, + [], + query, + [:left, :inner, :root] + ) + + if is_nil(binding) do + raise "Error while constructing explicit `binding()` reference." + end + + {:ok, Ecto.Query.dynamic([{^binding, row}], row), acc} + end + def expr( query, %like{arguments: [arg1, arg2], embedded?: pred_embedded?}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 186cf112..e15dbbc8 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -800,6 +800,20 @@ defmodule AshPostgres.CalculationTest do |> Ash.read_one!() end + test "binding() can be used to refer to the current binding in a fragment" do + post = + Post + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + post_id = post.id + + assert [%{id: ^post_id}] = + Post + |> Ash.Query.filter(fragment("(?).id", binding()) == type(^post.id, :uuid)) + |> Ash.read!() + end + test "exists with a relationship that has a filtered read action works" do post = Post From 39c5a9daf4685247939534c4e9b4ebb628497c74 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 13:52:40 -0400 Subject: [PATCH 0577/1215] chore: update ash_sql version --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 954dcb4a..06be10c8 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.1.7")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.18")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.20")}, {:igniter, "~> 0.2.9"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index ca537d5f..f937365c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.1.8", "427fa57ad03185cc3e4ea83179d8a07e8158578ce4048e7485a4f2f9375c4329", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9749695ce573efc3cc9f799697e3aeb3e69e9d4409e90aefc410d55309044182"}, - "ash_sql": {:hex, :ash_sql, "0.2.18", "e88b316ae6b820e0b19ddb4487662b24ef661a48de3ae836dda3d18f6de66556", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b43c7dfc1136377d55f9082d752503ff1afadccc3aa4d4abde7c09be5e025ef2"}, + "ash_sql": {:hex, :ash_sql, "0.2.20", "f4e4fbed0954f0652bd5ace2f31f90eabd2e24470d450f66a31905cd381422cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "41a0fdc2e26d6b4b750d4a4b2404ceeae7e9e240bc37052fc2179641a66b6cb3"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -35,7 +35,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, - "spark": {:hex, :spark, "2.2.7", "96113e09a52a2a95fd696e06f310950132aabfacf5c7b34e0666d26ce4a7b7a7", [:mix], [{:igniter, "~> 0.2.6", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e192add56a260382d4d270e1490401786f96545b86d67b466544cecb48c3f9a4"}, + "spark": {:hex, :spark, "2.2.8", "f61b5097c43c8562df0e78720be863d21c1c63bc4679c5cdbc8ef063276479de", [:mix], [{:igniter, "~> 0.2.6", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "309db664494a4dd08513476b8f77a65ad513b642773f193e7af6c2181c9b782d"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From a2efec05a6921a1c83162fdeb02ade3fa3fc969f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 13:53:00 -0400 Subject: [PATCH 0578/1215] chore: release version v2.1.5 --- CHANGELOG.md | 17 +++++++++++++++++ mix.exs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 221cd765..3f82cd3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.5](https://github.com/ash-project/ash_postgres/compare/v2.1.4...v2.1.5) (2024-07-15) + + + + +### Bug Fixes: + +* ensure synthesized query aggregates have context set + +### Improvements: + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.4](https://github.com/ash-project/ash_postgres/compare/v2.1.3...v2.1.4) (2024-07-14) diff --git a/mix.exs b/mix.exs index 06be10c8..a3da1315 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.4" + @version "2.1.5" def project do [ From 6b100a05595b415927e147fc5ce83593849aafa1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 13:54:29 -0400 Subject: [PATCH 0579/1215] chore: update changelog --- CHANGELOG.md | 23 +++++------------------ lib/sql_implementation.ex | 15 ++++++++------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f82cd3f..e8383bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,40 +7,27 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.1.5](https://github.com/ash-project/ash_postgres/compare/v2.1.4...v2.1.5) (2024-07-15) - - - ### Bug Fixes: -* ensure synthesized query aggregates have context set +- ensure synthesized query aggregates have context set ### Improvements: -* add `binding()` expression +- [`Ash.Expr`] add `binding()` expression to refer to current table -* use latest type casting code from ash - -* support new type determination code +- [`Ash.Expr`] use latest type casting code from ash ## [v2.1.4](https://github.com/ash-project/ash_postgres/compare/v2.1.3...v2.1.4) (2024-07-14) - - - ### Improvements: -* use latest type casting code from ash - -* support new type determination code +- [`Ash.Expr`] use latest type casting code from ash ## [v2.1.3](https://github.com/ash-project/ash_postgres/compare/v2.1.2...v2.1.3) (2024-07-14) - - - ### Improvements: -* support new type determination code +- [`Ash.Expr`] support new type determination code ## [v2.1.2](https://github.com/ash-project/ash_postgres/compare/v2.1.1...v2.1.2) (2024-07-13) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 90414cf3..7c556076 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -42,15 +42,16 @@ defmodule AshPostgres.SqlImplementation do end def expr(query, %AshPostgres.Functions.Binding{}, _bindings, _embedded?, acc, _type) do - binding = AshSql.Bindings.get_binding( - query.__ash_bindings__.resource, - [], - query, - [:left, :inner, :root] - ) + binding = + AshSql.Bindings.get_binding( + query.__ash_bindings__.resource, + [], + query, + [:left, :inner, :root] + ) if is_nil(binding) do - raise "Error while constructing explicit `binding()` reference." + raise "Error while constructing explicit `binding()` reference." end {:ok, Ecto.Query.dynamic([{^binding, row}], row), acc} From 8cd8650f5a81f236aea1d8af66e707833a36b3f6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 22:01:18 -0400 Subject: [PATCH 0580/1215] improvement: update ash/igniter dependencies --- mix.exs | 2 +- mix.lock | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index a3da1315..cbd2f714 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.1 and >= 3.1.7")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.20")}, - {:igniter, "~> 0.2.9"}, + {:igniter, "~> 0.3"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index f937365c..746d2c57 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.1.8", "427fa57ad03185cc3e4ea83179d8a07e8158578ce4048e7485a4f2f9375c4329", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.2.12", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9749695ce573efc3cc9f799697e3aeb3e69e9d4409e90aefc410d55309044182"}, + "ash": {:hex, :ash, "3.2.0", "9569a7d31ebbb218bbef652235989a9943a8cd0775cf8dfad3004b8399e8a8e6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aef9a42268462fc9756e5f7152a5fba54707d632787c726d4b3db462d6301d8c"}, "ash_sql": {:hex, :ash_sql, "0.2.20", "f4e4fbed0954f0652bd5ace2f31f90eabd2e24470d450f66a31905cd381422cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "41a0fdc2e26d6b4b750d4a4b2404ceeae7e9e240bc37052fc2179641a66b6cb3"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "igniter": {:hex, :igniter, "0.2.13", "dbf743887f73de135f38a121e31dd16ce29ea355b1de85c876848fecf335ca0a", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f6e7e98ac9f42ca5fa6121d4212c6d462049e43fbf91ec56e3b2cf2895eba86b"}, + "igniter": {:hex, :igniter, "0.3.2", "f1a60895c0ee40543efd8f8c57f3eb07373e97320e7673272589e611f3f8ac30", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "b8ffae7e6860ffdc0abe89d0cf4104c110bf1296748600de1e5744e03ff2e663"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -29,19 +29,21 @@ "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "owl": {:hex, :owl, "0.9.0", "9b33d64734bd51d3fc1d6ed01b12f8c2ed23e1fbf8c43658a6dfbff62578bd03", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "cd70b55327985f8f24d38cb7de5bf8a8d24040e1b49cca2345508f8119ce81fd"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.8.5", "7a621e0392a5975ed97938a4ddbbc92a6a31157fbd87446bc8bc6b1a0f49e56a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17b1976b9d333e55382dc108779078d5bbdbcd2c3d4033ea6dd52437339fe469"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, - "spark": {:hex, :spark, "2.2.8", "f61b5097c43c8562df0e78720be863d21c1c63bc4679c5cdbc8ef063276479de", [:mix], [{:igniter, "~> 0.2.6", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "309db664494a4dd08513476b8f77a65ad513b642773f193e7af6c2181c9b782d"}, + "spark": {:hex, :spark, "2.2.8", "e146eabeada4aec2a0aa1952ab6af385bee5d2b4b9145f4acb0f9d1450769685", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b0c366c429e65cb2f132a3b308fb6ec7f29daff8388853e1c13f59b51abd9a7a"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, + "ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, } From de69ec076fe7b51857d19f0a6e6ba4f355cd05c3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 15 Jul 2024 22:01:45 -0400 Subject: [PATCH 0581/1215] chore: release version v2.1.6 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8383bf6..b22569ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.6](https://github.com/ash-project/ash_postgres/compare/v2.1.5...v2.1.6) (2024-07-16) + + + + +### Bug Fixes: + +* ensure synthesized query aggregates have context set + +### Improvements: + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.5](https://github.com/ash-project/ash_postgres/compare/v2.1.4...v2.1.5) (2024-07-15) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index cbd2f714..421a8a52 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.5" + @version "2.1.6" def project do [ From a121ba177f44eaba888b2deb3eb74c9eeb7c64c0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 08:58:04 -0400 Subject: [PATCH 0582/1215] chore: test changes & update dependency --- mix.exs | 1 + mix.lock | 3 ++- test/aggregate_test.exs | 7 +++++++ test/support/resources/post.ex | 1 + test/support/resources/rating.ex | 4 ++++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 421a8a52..411a27c8 100644 --- a/mix.exs +++ b/mix.exs @@ -170,6 +170,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies + {:eflame, "~> 1.0", only: [:dev, :test]}, {:simple_sat, "~> 0.1", only: [:dev, :test]}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 746d2c57..43038c0f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.2.0", "9569a7d31ebbb218bbef652235989a9943a8cd0775cf8dfad3004b8399e8a8e6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aef9a42268462fc9756e5f7152a5fba54707d632787c726d4b3db462d6301d8c"}, - "ash_sql": {:hex, :ash_sql, "0.2.20", "f4e4fbed0954f0652bd5ace2f31f90eabd2e24470d450f66a31905cd381422cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "41a0fdc2e26d6b4b750d4a4b2404ceeae7e9e240bc37052fc2179641a66b6cb3"}, + "ash_sql": {:hex, :ash_sql, "0.2.22", "935243e93c32c0d28d8425428d876649708597b36f7b6035d4118f9684e7545c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "272938074895122cbd48fd05c04a2e1229bc7657b9d4690a41abd1adf4e3d3a9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -12,6 +12,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, + "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 588f6e41..af9c5003 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -5,6 +5,13 @@ defmodule AshSql.AggregateTest do require Ash.Query import Ash.Expr + test "nested sum aggregates" do + # asserting an error is not raised + assert Post + |> Ash.Query.load(:sum_of_comment_ratings_calc) + |> Ash.read!() == [] + end + test "relates to actor via has_many and with an aggregate" do org = Organization diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index dbcfe9f2..3fbb9af4 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -566,6 +566,7 @@ defmodule AshPostgres.Test.Post do end aggregates do + sum(:sum_of_comment_ratings_calc, [:comments, :ratings], :double_score) count(:count_of_comments, :comments) count(:count_of_linked_posts, :linked_posts) diff --git a/test/support/resources/rating.ex b/test/support/resources/rating.ex index 0006ea01..99509206 100644 --- a/test/support/resources/rating.ex +++ b/test/support/resources/rating.ex @@ -20,4 +20,8 @@ defmodule AshPostgres.Test.Rating do attribute(:score, :integer, public?: true) attribute(:resource_id, :uuid, public?: true) end + + calculations do + calculate(:double_score, :integer, expr(score * 2)) + end end From 1e637f9205b7ebe1774fc47e679b56b1ffc73aa3 Mon Sep 17 00:00:00 2001 From: Robert Timis <65460527+TimisRobert@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:39:04 +0200 Subject: [PATCH 0583/1215] test: replicate error when building json object and using inline aggregate (#350) --- test/complex_calculations_test.exs | 17 +++++++++++++++++ .../resources/documentation.ex | 2 ++ .../complex_calculations/resources/skill.ex | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index da13ab30..a1268c0b 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -86,6 +86,23 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do assert certification.count_of_skills_ever_demonstrated == 1 end + test "calculation of inline aggregate" do + skill = + AshPostgres.Test.ComplexCalculations.Skill + |> Ash.Changeset.new() + |> Ash.create!() + + AshPostgres.Test.ComplexCalculations.Documentation + |> Ash.Changeset.for_create(:create, %{status: :demonstrated}) + |> Ash.Changeset.manage_relationship(:skill, skill, type: :append) + |> Ash.create!() + + skill = Ash.load!(skill, [:documentations_custom]) + + assert %{one: "One", documentations: [%{two: "Two", status: :demonstrated}]} = + skill.documentations_custom + end + test "channel: first_member and second member" do channel = AshPostgres.Test.ComplexCalculations.Channel diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex index 7ec79404..ad961895 100644 --- a/test/support/complex_calculations/resources/documentation.ex +++ b/test/support/complex_calculations/resources/documentation.ex @@ -29,6 +29,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do end calculations do + calculate(:custom_map, :map, expr(%{status: status, two: "Two"})) + calculate( :timestamp, :utc_datetime_usec, diff --git a/test/support/complex_calculations/resources/skill.ex b/test/support/complex_calculations/resources/skill.ex index 7715900e..6c81ceda 100644 --- a/test/support/complex_calculations/resources/skill.ex +++ b/test/support/complex_calculations/resources/skill.ex @@ -39,6 +39,12 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do ) end + calculate( + :documentations_custom, + :map, + expr(%{one: "One", documentations: list(documentations, field: :custom_map)}) + ) + calculate :count_ever_demonstrated, :integer do calculation( expr( From 2393c2c633c8bc0424a69c2ccbbc39afaa3d44b5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 18:22:23 -0400 Subject: [PATCH 0584/1215] chore: update tests to account for non-keepable-atom-keys --- test/complex_calculations_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index a1268c0b..dfa5e13d 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -99,7 +99,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do skill = Ash.load!(skill, [:documentations_custom]) - assert %{one: "One", documentations: [%{two: "Two", status: :demonstrated}]} = + assert %{one: "One", documentations: [%{"two" => "Two", "status" => "demonstrated"}]} = skill.documentations_custom end From 7cf06f395688fe1b1c3446115263ac322bd555ac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 22:39:51 -0400 Subject: [PATCH 0585/1215] fix: update ash_sql for include_nil? fix and test it --- mix.exs | 2 +- mix.lock | 2 +- test/aggregate_test.exs | 56 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 5 +++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 411a27c8..bc72da72 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.1.7")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.20")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.23")}, {:igniter, "~> 0.3"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 43038c0f..b6b2b2de 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.2.0", "9569a7d31ebbb218bbef652235989a9943a8cd0775cf8dfad3004b8399e8a8e6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aef9a42268462fc9756e5f7152a5fba54707d632787c726d4b3db462d6301d8c"}, - "ash_sql": {:hex, :ash_sql, "0.2.22", "935243e93c32c0d28d8425428d876649708597b36f7b6035d4118f9684e7545c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "272938074895122cbd48fd05c04a2e1229bc7657b9d4690a41abd1adf4e3d3a9"}, + "ash_sql": {:hex, :ash_sql, "0.2.23", "8e00592827bfc99d1d8410e600d053da99f931007a4e87eeb579a2222aa02549", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e1902d694181e03202d3fa4b888e62146bb86ab1ffd9e45bde8d3992fa08e229"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index af9c5003..6f01db92 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -378,6 +378,62 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + test "does not return nil values" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "bbb"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: nil}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "aaa"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{comment_titles: ["aaa", "bbb"]} = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load(:comment_titles) + |> Ash.read_one!() + end + + test "returns nil values if `include_nil?` is set to `true`" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "bbb"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: nil}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "aaa"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{comment_titles_with_nils: ["aaa", "bbb", nil]} = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load(:comment_titles_with_nils) + |> Ash.read_one!() + end + test "with related data, it returns the value" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 3fbb9af4..03f5c85a 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -606,6 +606,11 @@ defmodule AshPostgres.Test.Post do sort(title: :asc_nils_last) end + list :comment_titles_with_nils, :comments, :title do + sort(title: :asc_nils_last) + include_nil?(true) + end + list :uniq_comment_titles, :comments, :title do uniq?(true) sort(title: :asc_nils_last) From 67ab07abcea329fb5e144e8e9b1ed034f866961b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 22:42:53 -0400 Subject: [PATCH 0586/1215] fix: update to latest ash version for aggregate fix --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index bc72da72..72709157 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.1 and >= 3.1.7")}, + {:ash, ash_version("~> 3.1 and >= 3.2.1")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.23")}, {:igniter, "~> 0.3"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, diff --git a/mix.lock b/mix.lock index b6b2b2de..df76634f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.2.0", "9569a7d31ebbb218bbef652235989a9943a8cd0775cf8dfad3004b8399e8a8e6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aef9a42268462fc9756e5f7152a5fba54707d632787c726d4b3db462d6301d8c"}, + "ash": {:hex, :ash, "3.2.1", "cc9b2b1d8ceaf795ff4cf26a09914eeb4e1fa3105bb34e60f5a32690bac83e3c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c70299b3b0f37ebb9f6619fc85f3dd030f493a946cc3941e84aa3351142add5"}, "ash_sql": {:hex, :ash_sql, "0.2.23", "8e00592827bfc99d1d8410e600d053da99f931007a4e87eeb579a2222aa02549", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e1902d694181e03202d3fa4b888e62146bb86ab1ffd9e45bde8d3992fa08e229"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From d5f5bae117f8158fba92e8546261be7c673b2718 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 22:58:14 -0400 Subject: [PATCH 0587/1215] test: add tests for `include_nil?` on `first` aggregates as well --- CHANGELOG.md | 23 +++++++++++++++++ mix.exs | 2 +- test/aggregate_test.exs | 46 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 9 +++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b22569ff..b3a6c5f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.7](https://github.com/ash-project/ash_postgres/compare/v2.1.6...v2.1.7) (2024-07-17) + + + + +### Bug Fixes: + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.6](https://github.com/ash-project/ash_postgres/compare/v2.1.5...v2.1.6) (2024-07-16) diff --git a/mix.exs b/mix.exs index 72709157..198c9851 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.6" + @version "2.1.7" def project do [ diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 6f01db92..9c8c27d8 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -577,6 +577,52 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + test "it does not return `nil` values by default" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: nil}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{first_comment_nils_first: "match"} = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load(:first_comment_nils_first) + |> Ash.read_one!() + end + + test "it returns `nil` values when `include_nil?` is `true`" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: nil}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{first_comment_nils_first_include_nil: "match"} = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load(:first_comment_nils_first_include_nil) + |> Ash.read_one!() + end + test "it can be sorted on" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 03f5c85a..e99bff53 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -584,6 +584,15 @@ defmodule AshPostgres.Test.Post do sort(title: :asc_nils_last) end + first :first_comment_nils_first, :comments, :title do + sort(title: :asc_nils_first) + end + + first :first_comment_nils_first_include_nil, :comments, :title do + include_nil?(true) + sort(title: :asc_nils_first) + end + first :last_comment, :comments, :title do sort(title: :desc, title: :asc) end From aedbaeeb10f5176a9dadc8b7fca2e37111a06f02 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 23:01:10 -0400 Subject: [PATCH 0588/1215] chore: release version v2.1.8 --- CHANGELOG.md | 17 +++++++++++++++++ mix.exs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a6c5f4..abc7f478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.8](https://github.com/ash-project/ash_postgres/compare/v2.1.7...v2.1.8) (2024-07-17) + + + + +### Bug Fixes: + +* [aggregates] update ash_sql & ash for include_nil? fix (and test it) + +* [aggregates] ensure synthesized query aggregates have context set + +### Improvements: + +* [installers] update igniter dependencies + +* [expressions] add `binding()` expression, for referring to the current table + ## [v2.1.7](https://github.com/ash-project/ash_postgres/compare/v2.1.6...v2.1.7) (2024-07-17) diff --git a/mix.exs b/mix.exs index 198c9851..0c8cbccb 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.7" + @version "2.1.8" def project do [ From 455106be7eb5bf4508533a3954d66b4a2ba57778 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 16 Jul 2024 23:10:18 -0400 Subject: [PATCH 0589/1215] chore: update changelog --- CHANGELOG.md | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abc7f478..6e9467a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,62 +7,53 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.1.8](https://github.com/ash-project/ash_postgres/compare/v2.1.7...v2.1.8) (2024-07-17) - - - ### Bug Fixes: -* [aggregates] update ash_sql & ash for include_nil? fix (and test it) +- [aggregates] update ash_sql & ash for include_nil? fix (and test it) -* [aggregates] ensure synthesized query aggregates have context set +- [aggregates] ensure synthesized query aggregates have context set ### Improvements: -* [installers] update igniter dependencies +- [installers] update igniter dependencies -* [expressions] add `binding()` expression, for referring to the current table +- [expressions] add `binding()` expression, for referring to the current table ## [v2.1.7](https://github.com/ash-project/ash_postgres/compare/v2.1.6...v2.1.7) (2024-07-17) - - - ### Bug Fixes: -* update to latest ash version for aggregate fix +- update to latest ash version for aggregate fix -* update ash_sql for include_nil? fix and test it +- update ash_sql for include_nil? fix and test it -* ensure synthesized query aggregates have context set +- ensure synthesized query aggregates have context set ### Improvements: -* update ash/igniter dependencies +- update ash/igniter dependencies -* add `binding()` expression +- add `binding()` expression -* use latest type casting code from ash +- use latest type casting code from ash -* support new type determination code +- support new type determination code ## [v2.1.6](https://github.com/ash-project/ash_postgres/compare/v2.1.5...v2.1.6) (2024-07-16) - - - ### Bug Fixes: -* ensure synthesized query aggregates have context set +- ensure synthesized query aggregates have context set ### Improvements: -* update ash/igniter dependencies +- update ash/igniter dependencies -* add `binding()` expression +- add `binding()` expression -* use latest type casting code from ash +- use latest type casting code from ash -* support new type determination code +- support new type determination code ## [v2.1.5](https://github.com/ash-project/ash_postgres/compare/v2.1.4...v2.1.5) (2024-07-15) From 64740bac186480687cb904d3745779f1307568f2 Mon Sep 17 00:00:00 2001 From: Robert Timis <65460527+TimisRobert@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:09:45 +0200 Subject: [PATCH 0590/1215] test: replicate error when using calculation over nested relationship (#351) --- config/config.exs | 3 +- .../test_repo/items/20240717104854.json | 69 +++++++++++++++++++ ...7104854_no_attributes_calculation_test.exs | 23 +++++++ test/multi_domain_calculations_test.exs | 28 ++++++++ .../domain_one/item.ex | 10 ++- .../multi_domain_calculations/domain_three.ex | 8 +++ .../domain_three/relationship_item.ex | 31 +++++++++ .../domain_two/other_item.ex | 5 ++ .../domain_two/sub_item.ex | 6 ++ 9 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/items/20240717104854.json create mode 100644 priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs create mode 100644 test/support/multi_domain_calculations/domain_three.ex create mode 100644 test/support/multi_domain_calculations/domain_three/relationship_item.ex diff --git a/config/config.exs b/config/config.exs index 4fc036c7..6861abc5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -54,7 +54,8 @@ if Mix.env() == :test do AshPostgres.MultitenancyTest.Domain, AshPostgres.Test.ComplexCalculations.Domain, AshPostgres.Test.MultiDomainCalculations.DomainOne, - AshPostgres.Test.MultiDomainCalculations.DomainTwo + AshPostgres.Test.MultiDomainCalculations.DomainTwo, + AshPostgres.Test.MultiDomainCalculations.DomainThree ] config :ash, :compatible_foreign_key_types, [ diff --git a/priv/resource_snapshots/test_repo/items/20240717104854.json b/priv/resource_snapshots/test_repo/items/20240717104854.json new file mode 100644 index 00000000..4339840d --- /dev/null +++ b/priv/resource_snapshots/test_repo/items/20240717104854.json @@ -0,0 +1,69 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "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": "key", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "value", + "type": "bigint" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "4C97C976528D44A7E55E873A1EF3539C3778EC7EFE3EC5AF644C0A736EF679FC", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "items" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs b/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs new file mode 100644 index 00000000..04a9f3f6 --- /dev/null +++ b/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs @@ -0,0 +1,23 @@ +defmodule AshPostgres.TestRepo.Migrations.NoAttributesCalculationTest 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(:items) do + add(:key, :text) + add(:value, :bigint) + end + end + + def down do + alter table(:items) do + remove(:value) + remove(:key) + end + end +end diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs index bf659855..8a9f456b 100644 --- a/test/multi_domain_calculations_test.exs +++ b/test/multi_domain_calculations_test.exs @@ -25,4 +25,32 @@ defmodule AshPostgres.Test.MultiDomainCalculationsTest do load: [:total_amount] ) end + + test "total using relationship is returned correctly" do + item = + AshPostgres.Test.MultiDomainCalculations.DomainOne.Item + |> Ash.Changeset.for_create(:create, %{key: "key"}) + |> Ash.create!() + + _relationship_item = + AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem + |> Ash.Changeset.for_create(:create, %{key: "key", value: 1}) + |> Ash.create!() + + other_item = + AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem + |> Ash.Changeset.for_create(:create, %{item_id: item.id}) + |> Ash.create!() + + for i <- 0..2 do + AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem + |> Ash.Changeset.for_create(:create, %{other_item_id: other_item.id, amount: i}) + |> Ash.create!() + end + + assert [%{total_amount: 3}] = + Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, + load: [:total_amount_relationship] + ) + end end diff --git a/test/support/multi_domain_calculations/domain_one/item.ex b/test/support/multi_domain_calculations/domain_one/item.ex index 5d76e9eb..36090798 100644 --- a/test/support/multi_domain_calculations/domain_one/item.ex +++ b/test/support/multi_domain_calculations/domain_one/item.ex @@ -6,24 +6,32 @@ defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne.Item do authorizers: [Ash.Policy.Authorizer], domain: AshPostgres.Test.MultiDomainCalculations.DomainOne + alias AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem alias AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem attributes do uuid_v7_primary_key(:id) + attribute(:key, :string) create_timestamp(:inserted_at) update_timestamp(:updated_at) end relationships do has_one(:other_item, OtherItem) + + has_one(:relationship_item, RelationshipItem) do + no_attributes?(true) + filter(expr(parent(key) == key)) + end end actions do - defaults([:read, :destroy, update: :*, create: :*]) + defaults([:read, :destroy, update: :*, create: [:*, :key]]) end calculations do calculate(:total_amount, :integer, expr(other_item.total_amount)) + calculate(:total_amount_relationship, :integer, expr(other_item.total_amount_relationship)) end policies do diff --git a/test/support/multi_domain_calculations/domain_three.ex b/test/support/multi_domain_calculations/domain_three.ex new file mode 100644 index 00000000..95eda02d --- /dev/null +++ b/test/support/multi_domain_calculations/domain_three.ex @@ -0,0 +1,8 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree do + @moduledoc false + use Ash.Domain + + resources do + resource(AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem) + end +end diff --git a/test/support/multi_domain_calculations/domain_three/relationship_item.ex b/test/support/multi_domain_calculations/domain_three/relationship_item.ex new file mode 100644 index 00000000..fe7aa8c8 --- /dev/null +++ b/test/support/multi_domain_calculations/domain_three/relationship_item.ex @@ -0,0 +1,31 @@ +defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem do + @moduledoc false + + use Ash.Resource, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer], + domain: AshPostgres.Test.MultiDomainCalculations.DomainThree + + attributes do + uuid_v7_primary_key(:id) + attribute(:key, :string, allow_nil?: false) + attribute(:value, :integer, allow_nil?: false) + create_timestamp(:inserted_at) + update_timestamp(:updated_at) + end + + actions do + defaults([:read, :destroy, update: :*, create: [:*, :key, :value]]) + end + + policies do + policy always() do + authorize_if(always()) + end + end + + postgres do + table "items" + repo(AshPostgres.TestRepo) + end +end diff --git a/test/support/multi_domain_calculations/domain_two/other_item.ex b/test/support/multi_domain_calculations/domain_two/other_item.ex index 60cdea7a..1bd3e754 100644 --- a/test/support/multi_domain_calculations/domain_two/other_item.ex +++ b/test/support/multi_domain_calculations/domain_two/other_item.ex @@ -28,10 +28,15 @@ defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem do sum :total_sub_items_amount, :sub_items, :total_amount do default(0) end + + sum :total_sub_items_relationship_amount, :sub_items, :total_amount_relationship do + default(0) + end end calculations do calculate(:total_amount, :integer, expr(total_sub_items_amount)) + calculate(:total_amount_relationship, :integer, expr(total_sub_items_relationship_amount)) end policies do diff --git a/test/support/multi_domain_calculations/domain_two/sub_item.ex b/test/support/multi_domain_calculations/domain_two/sub_item.ex index c5411e51..c9f8b491 100644 --- a/test/support/multi_domain_calculations/domain_two/sub_item.ex +++ b/test/support/multi_domain_calculations/domain_two/sub_item.ex @@ -25,6 +25,12 @@ defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem do calculations do calculate(:total_amount, :integer, expr(amount)) + + calculate( + :total_amount_relationship, + :integer, + expr(amount * other_item.item.relationship_item.value) + ) end policies do From 10983856b11cbcd48ef9e5ec12c156b7183053fe Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 17 Jul 2024 11:40:17 -0400 Subject: [PATCH 0591/1215] chore: update tests/migrations --- .../test_repo/items/20240717153736.json | 59 ++++++++++++ .../test_repo/other_items/20240717151815.json | 93 +++++++++++++++++++ .../relationship_items/20240717153736.json | 69 ++++++++++++++ .../20240717151815_migrate_resources36.exs | 19 ++++ .../20240717153736_migrate_resources37.exs | 39 ++++++++ test/multi_domain_calculations_test.exs | 6 +- .../domain_three/relationship_item.ex | 2 +- .../domain_two/other_item.ex | 4 + 8 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/items/20240717153736.json create mode 100644 priv/resource_snapshots/test_repo/other_items/20240717151815.json create mode 100644 priv/resource_snapshots/test_repo/relationship_items/20240717153736.json create mode 100644 priv/test_repo/migrations/20240717151815_migrate_resources36.exs create mode 100644 priv/test_repo/migrations/20240717153736_migrate_resources37.exs diff --git a/priv/resource_snapshots/test_repo/items/20240717153736.json b/priv/resource_snapshots/test_repo/items/20240717153736.json new file mode 100644 index 00000000..445e6467 --- /dev/null +++ b/priv/resource_snapshots/test_repo/items/20240717153736.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "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": "key", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "571E95D3DAC0A3C7446601D96857C70384A20159928EE335D3A421FA61555158", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/other_items/20240717151815.json b/priv/resource_snapshots/test_repo/other_items/20240717151815.json new file mode 100644 index 00000000..8a057d24 --- /dev/null +++ b/priv/resource_snapshots/test_repo/other_items/20240717151815.json @@ -0,0 +1,93 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "other_items_item_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "items" + }, + "size": null, + "source": "item_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "43CBB861ACD2A789B2871FAF147C4480E7854D422D70213A4DB321CECC68A203", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "other_items_unique_parent_index", + "keys": [ + { + "type": "atom", + "value": "item_id" + } + ], + "name": "unique_parent", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "other_items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json b/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json new file mode 100644 index 00000000..4d5a8093 --- /dev/null +++ b/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json @@ -0,0 +1,69 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v7()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "key", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "value", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "FC7547C74057DE3A87306D14A0AD1621D9BE7812D8BF94D68D8B319C5AA84C55", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "relationship_items" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240717151815_migrate_resources36.exs b/priv/test_repo/migrations/20240717151815_migrate_resources36.exs new file mode 100644 index 00000000..1a885fcf --- /dev/null +++ b/priv/test_repo/migrations/20240717151815_migrate_resources36.exs @@ -0,0 +1,19 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources36 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 + create(unique_index(:other_items, [:item_id], name: "other_items_unique_parent_index")) + end + + def down do + drop_if_exists( + unique_index(:other_items, [:item_id], name: "other_items_unique_parent_index") + ) + end +end diff --git a/priv/test_repo/migrations/20240717153736_migrate_resources37.exs b/priv/test_repo/migrations/20240717153736_migrate_resources37.exs new file mode 100644 index 00000000..ba2f6bfe --- /dev/null +++ b/priv/test_repo/migrations/20240717153736_migrate_resources37.exs @@ -0,0 +1,39 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources37 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 + create table(:relationship_items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true) + add(:key, :text, null: false) + add(:value, :bigint, null: false) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + + alter table(:items) do + remove(:value) + end + end + + def down do + alter table(:items) do + add(:value, :bigint) + end + + drop(table(:relationship_items)) + end +end diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs index 8a9f456b..8b079073 100644 --- a/test/multi_domain_calculations_test.exs +++ b/test/multi_domain_calculations_test.exs @@ -32,6 +32,10 @@ defmodule AshPostgres.Test.MultiDomainCalculationsTest do |> Ash.Changeset.for_create(:create, %{key: "key"}) |> Ash.create!() + Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, + load: [:total_amount_relationship] + ) + _relationship_item = AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem |> Ash.Changeset.for_create(:create, %{key: "key", value: 1}) @@ -48,7 +52,7 @@ defmodule AshPostgres.Test.MultiDomainCalculationsTest do |> Ash.create!() end - assert [%{total_amount: 3}] = + assert [%{total_amount_relationship: 3}] = Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, load: [:total_amount_relationship] ) diff --git a/test/support/multi_domain_calculations/domain_three/relationship_item.ex b/test/support/multi_domain_calculations/domain_three/relationship_item.ex index fe7aa8c8..b90b93a7 100644 --- a/test/support/multi_domain_calculations/domain_three/relationship_item.ex +++ b/test/support/multi_domain_calculations/domain_three/relationship_item.ex @@ -25,7 +25,7 @@ defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem end postgres do - table "items" + table "relationship_items" repo(AshPostgres.TestRepo) end end diff --git a/test/support/multi_domain_calculations/domain_two/other_item.ex b/test/support/multi_domain_calculations/domain_two/other_item.ex index 1bd3e754..2e01406e 100644 --- a/test/support/multi_domain_calculations/domain_two/other_item.ex +++ b/test/support/multi_domain_calculations/domain_two/other_item.ex @@ -15,6 +15,10 @@ defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem do update_timestamp(:updated_at) end + identities do + identity :unique_parent, [:item_id] + end + relationships do belongs_to(:item, Item, allow_nil?: false) has_many(:sub_items, SubItem) From 0bf9c4e9ec07deb92b1623e381d83d0ed2f58e2e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 17 Jul 2024 11:41:42 -0400 Subject: [PATCH 0592/1215] fix: update `ash_sql` for `parent_as` binding fix --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 0c8cbccb..c167c648 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.2.1")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.23")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.24")}, {:igniter, "~> 0.3"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index df76634f..8c7fc012 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.2.1", "cc9b2b1d8ceaf795ff4cf26a09914eeb4e1fa3105bb34e60f5a32690bac83e3c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c70299b3b0f37ebb9f6619fc85f3dd030f493a946cc3941e84aa3351142add5"}, - "ash_sql": {:hex, :ash_sql, "0.2.23", "8e00592827bfc99d1d8410e600d053da99f931007a4e87eeb579a2222aa02549", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e1902d694181e03202d3fa4b888e62146bb86ab1ffd9e45bde8d3992fa08e229"}, + "ash_sql": {:hex, :ash_sql, "0.2.24", "324f3fd65e249ad2e7df56a585a768edf6cc91246b7c8048ac16a684edc1437a", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "38f3066059be9f567ab5c3a8d99e812417069b881bc12fa33c067b1ea7742145"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -37,7 +37,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, - "spark": {:hex, :spark, "2.2.8", "e146eabeada4aec2a0aa1952ab6af385bee5d2b4b9145f4acb0f9d1450769685", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b0c366c429e65cb2f132a3b308fb6ec7f29daff8388853e1c13f59b51abd9a7a"}, + "spark": {:hex, :spark, "2.2.9", "cc86e39895e1e1b2360e333fe37fa8cdb5624d265d234c0c945b1b1e11b49563", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "d855e3971f568527bdd43377a8088ae96a7798a1df732c9b7a631730ba2f705d"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 0cac01fe30002e655634e24d8bfd1c69e57814f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 08:33:38 -0400 Subject: [PATCH 0593/1215] chore(deps): bump ash in the production-dependencies group (#352) Bumps the production-dependencies group with 1 update: [ash](https://github.com/ash-project/ash). Updates `ash` from 3.2.1 to 3.2.2 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.2.1...v3.2.2) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8c7fc012..a4f140d9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.2.1", "cc9b2b1d8ceaf795ff4cf26a09914eeb4e1fa3105bb34e60f5a32690bac83e3c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c70299b3b0f37ebb9f6619fc85f3dd030f493a946cc3941e84aa3351142add5"}, + "ash": {:hex, :ash, "3.2.2", "f079fbe7f4f7e3279825c841aa69f6b83333a86267b31435ca97031a805c4b41", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b9a9165d1aafec1fa719df6182ddee0b2a29c83f67079a04a9a8060f49cc1e7f"}, "ash_sql": {:hex, :ash_sql, "0.2.24", "324f3fd65e249ad2e7df56a585a768edf6cc91246b7c8048ac16a684edc1437a", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "38f3066059be9f567ab5c3a8d99e812417069b881bc12fa33c067b1ea7742145"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 4cb3a72c54d42ead9619672f538d5ceab8416a08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 08:34:08 -0400 Subject: [PATCH 0594/1215] chore(deps-dev): bump mix_audit in the dev-dependencies group (#353) Bumps the dev-dependencies group with 1 update: [mix_audit](https://github.com/mirego/mix_audit). Updates `mix_audit` from 2.1.3 to 2.1.4 - [Changelog](https://github.com/mirego/mix_audit/blob/main/CHANGELOG.md) - [Commits](https://github.com/mirego/mix_audit/compare/v2.1.3...v2.1.4) --- updated-dependencies: - dependency-name: mix_audit dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index a4f140d9..c9f59dae 100644 --- a/mix.lock +++ b/mix.lock @@ -27,7 +27,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"}, + "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.9.0", "9b33d64734bd51d3fc1d6ed01b12f8c2ed23e1fbf8c43658a6dfbff62578bd03", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "cd70b55327985f8f24d38cb7de5bf8a8d24040e1b49cca2345508f8119ce81fd"}, @@ -46,5 +46,5 @@ "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, - "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, } From c63c988339451277685803802e685eaf312e1012 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 18 Jul 2024 12:52:27 -0400 Subject: [PATCH 0595/1215] improvement: pluralize table name in extender chore: get build passing & clean up code closes #354 --- .tool-versions | 4 +-- lib/data_layer.ex | 29 +++++++++---------- mix.exs | 2 +- mix.lock | 3 +- test/multi_domain_calculations_test.exs | 6 ++-- .../domain_two/other_item.ex | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.tool-versions b/.tool-versions index 61c2916b..ce0360e0 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 27.0 -elixir 1.17.0 +erlang 27.0.1 +elixir 1.17.2-otp-27 diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 450acc8d..1602df49 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2916,24 +2916,23 @@ defmodule AshPostgres.DataLayer do end end - if Code.ensure_loaded?(Igniter) do - def install(igniter, module, Ash.Resource, _path, _argv) do - table_name = - module - |> Module.split() - |> List.last() - |> Macro.underscore() - - repo = Igniter.Code.Module.module_name("Repo") - - igniter - |> Spark.Igniter.set_option(module, [:postgres, :table], table_name) - |> Spark.Igniter.set_option(module, [:postgres, :repo], repo) - end + def install(igniter, module, Ash.Resource, _path, _argv) do + table_name = + module + |> Module.split() + |> List.last() + |> Macro.underscore() + |> Inflex.pluralize() + + repo = Igniter.Code.Module.module_name("Repo") - def install(igniter, _, _, _), do: igniter + igniter + |> Spark.Igniter.set_option(module, [:postgres, :table], table_name) + |> Spark.Igniter.set_option(module, [:postgres, :repo], repo) end + def install(igniter, _, _, _), do: igniter + @impl true def rollback(resource, term) do AshPostgres.DataLayer.Info.repo(resource, :mutate).rollback(term) diff --git a/mix.exs b/mix.exs index c167c648..9fc30514 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.1 and >= 3.2.1")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.24")}, - {:igniter, "~> 0.3"}, + {:igniter, "~> 0.3 and >= 0.3.3"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index c9f59dae..a66a5c92 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,8 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "igniter": {:hex, :igniter, "0.3.2", "f1a60895c0ee40543efd8f8c57f3eb07373e97320e7673272589e611f3f8ac30", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "b8ffae7e6860ffdc0abe89d0cf4104c110bf1296748600de1e5744e03ff2e663"}, + "igniter": {:hex, :igniter, "0.3.3", "5cfc2c8b26df4c390d0238838ed9b993c1d0e12c4cae7e83e8ffbce342c7d3da", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "766429591d12022960ce358b019b11c2bf0fac172d3b09af2f848aabb86d8b95"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs index 8b079073..8cc9bc55 100644 --- a/test/multi_domain_calculations_test.exs +++ b/test/multi_domain_calculations_test.exs @@ -32,9 +32,9 @@ defmodule AshPostgres.Test.MultiDomainCalculationsTest do |> Ash.Changeset.for_create(:create, %{key: "key"}) |> Ash.create!() - Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, - load: [:total_amount_relationship] - ) + Ash.read!(AshPostgres.Test.MultiDomainCalculations.DomainOne.Item, + load: [:total_amount_relationship] + ) _relationship_item = AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem diff --git a/test/support/multi_domain_calculations/domain_two/other_item.ex b/test/support/multi_domain_calculations/domain_two/other_item.ex index 2e01406e..1524b47d 100644 --- a/test/support/multi_domain_calculations/domain_two/other_item.ex +++ b/test/support/multi_domain_calculations/domain_two/other_item.ex @@ -16,7 +16,7 @@ defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem do end identities do - identity :unique_parent, [:item_id] + identity(:unique_parent, [:item_id]) end relationships do From c47ed40a5504e7d7c1a1ece264c0a1ef8a4554e0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 18 Jul 2024 12:57:04 -0400 Subject: [PATCH 0596/1215] chore: release version v2.1.9 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e9467a1..83efac5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,33 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.9](https://github.com/ash-project/ash_postgres/compare/v2.1.8...v2.1.9) (2024-07-18) + + + + +### Bug Fixes: + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.8](https://github.com/ash-project/ash_postgres/compare/v2.1.7...v2.1.8) (2024-07-17) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 9fc30514..7f18ccde 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.8" + @version "2.1.9" def project do [ From 8f79fcb59ddaef5a9542cec24f374a6e21ea5f3f Mon Sep 17 00:00:00 2001 From: Jesse Williams Date: Thu, 18 Jul 2024 13:37:31 -0700 Subject: [PATCH 0597/1215] fix: allow non-unique has_many source_attributes (#355) * fix: allow non-unique has_many source_attributes * fix composite primary key case --- lib/data_layer.ex | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1602df49..b0676b61 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1044,9 +1044,43 @@ defmodule AshPostgres.DataLayer do {:ok, data_layer_query} -> source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) + source_filter = + case source_pkey do + [] -> + Ecto.Query.dynamic([source], field(source, ^source_attribute) in ^source_values) + + [field] -> + values = Enum.map(root_data, &Map.get(&1, field)) + Ecto.Query.dynamic([source], field(source, ^field) in ^values) + + fields -> + Enum.reduce(root_data, nil, fn record, acc -> + row_match = + Enum.reduce(fields, nil, fn field, acc -> + if is_nil(acc) do + Ecto.Query.dynamic( + [source], + field(source, ^field) == ^Map.get(record, field) + ) + else + Ecto.Query.dynamic( + [source], + field(source, ^field) == ^Map.get(record, field) and ^acc + ) + end + end) + + if is_nil(acc) do + row_match + else + Ecto.Query.dynamic(^row_match or ^acc) + end + end) + end + data_layer_query = from(source in data_layer_query, - where: field(source, ^source_attribute) in ^source_values + where: ^source_filter ) if query.__ash_bindings__[:__order__?] do From a1d0d7a97ad4d8aa5ff38b75d582f56ed6a4c4cd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 18 Jul 2024 18:48:40 -0400 Subject: [PATCH 0598/1215] chore: release version v2.1.10 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83efac5e..32baaa3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.10](https://github.com/ash-project/ash_postgres/compare/v2.1.9...v2.1.10) (2024-07-18) + + + + +### Bug Fixes: + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.9](https://github.com/ash-project/ash_postgres/compare/v2.1.8...v2.1.9) (2024-07-18) diff --git a/mix.exs b/mix.exs index 7f18ccde..fff00e8e 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.9" + @version "2.1.10" def project do [ From ff7b854c3e6e0e5985c06d83e2b6beaef08bb015 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 18 Jul 2024 18:51:04 -0400 Subject: [PATCH 0599/1215] chore: update changelog --- CHANGELOG.md | 48 ++---------------------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32baaa3f..c1f81a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,61 +7,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.1.10](https://github.com/ash-project/ash_postgres/compare/v2.1.9...v2.1.10) (2024-07-18) - - - ### Bug Fixes: -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - -### Improvements: - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code +- [lateral joins] allow non-unique has_many source_attributes (#355) ## [v2.1.9](https://github.com/ash-project/ash_postgres/compare/v2.1.8...v2.1.9) (2024-07-18) - - - ### Bug Fixes: -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - ### Improvements: -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code +- [`mix ash.gen.resource`] pluralize table name in extender ## [v2.1.8](https://github.com/ash-project/ash_postgres/compare/v2.1.7...v2.1.8) (2024-07-17) From 142e74f02650640fdde8ef7d43ff1079ce2a39c3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 08:34:34 -0400 Subject: [PATCH 0600/1215] improvement: prepend `:postgres` to section order --- lib/mix/tasks/ash_postgres.install.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 2231d4d8..ee048aec 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -17,6 +17,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> configure_test(otp_app, repo) |> setup_data_case() |> Igniter.Project.Application.add_new_child(repo) + |> Spark.Igniter.prepend_to_section_order(:"Ash.Resource", [:postgres]) |> Ash.Igniter.codegen("initialize") end From 8b5b5b4b6ba97fa810a0b7a78fa18eb9a9dd4880 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 12:34:06 -0400 Subject: [PATCH 0601/1215] fix: properly perform or don't perform configuration modification code --- lib/mix/tasks/ash_postgres.install.ex | 60 ++++++++++++++------------- mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index ee048aec..e58a80fe 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -2,6 +2,7 @@ defmodule Mix.Tasks.AshPostgres.Install do @moduledoc "Installs AshPostgres. Should be run with `mix igniter.install ash_postgres`" @shortdoc @moduledoc require Igniter.Code.Common + require Igniter.Code.Function use Igniter.Mix.Task def igniter(igniter, _argv) do @@ -77,35 +78,36 @@ defmodule Mix.Tasks.AshPostgres.Install do |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> - case Igniter.Code.Function.move_to_function_call_in_current_scope( - zipper, - :=, - 2, - fn call -> - Igniter.Code.Function.argument_matches_predicate?( - call, - 0, - &match?({:database_url, _, Elixir}, &1) - ) - end - ) do - {:ok, zipper} -> - zipper - |> Igniter.Project.Config.modify_configuration_code( - [repo, :url], - otp_app, - {:database_url, [], Elixir} - ) - |> Igniter.Project.Config.modify_configuration_code( - [repo, :pool_size], - otp_app, - quote do - String.to_integer(System.get_env("POOL_SIZE") || "10") - end - ) - |> then(&{:ok, &1}) - - :error -> + with {:ok, _zipper} <- + Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :=, + 2, + fn call -> + Igniter.Code.Function.argument_matches_pattern?( + call, + 0, + {:database_url, _, ctx} when is_atom(ctx) + ) + end + ) do + zipper + |> Igniter.Project.Config.modify_configuration_code( + [repo, :url], + otp_app, + {:database_url, [], nil} + ) + |> Igniter.Util.Debug.puts_code_at_node() + |> Igniter.Project.Config.modify_configuration_code( + [repo, :pool_size], + otp_app, + Sourceror.parse_string!(""" + String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + ) + |> then(&{:ok, &1}) + else + _ -> Igniter.Code.Common.add_code(zipper, """ database_url = System.get_env("DATABASE_URL") || diff --git a/mix.exs b/mix.exs index fff00e8e..d3e46547 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.1 and >= 3.2.1")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.24")}, - {:igniter, "~> 0.3 and >= 0.3.3"}, + {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index a66a5c92..1d361ab7 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "igniter": {:hex, :igniter, "0.3.3", "5cfc2c8b26df4c390d0238838ed9b993c1d0e12c4cae7e83e8ffbce342c7d3da", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "766429591d12022960ce358b019b11c2bf0fac172d3b09af2f848aabb86d8b95"}, + "igniter": {:hex, :igniter, "0.3.6", "30951d75604e88d6d893753e7331c9793625fbeb5fb34f06148e61ffcdca590f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "9fc32f2fc6eff9c24abe1d942a6632f5d50ccaeb031504064c1176f78f892b65"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From 938611f4bae84ea80d00f2e45878ea0f8d48d430 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 12:35:44 -0400 Subject: [PATCH 0602/1215] chore: release version v2.1.11 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f81a3e..9d8ae08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,41 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.11](https://github.com/ash-project/ash_postgres/compare/v2.1.10...v2.1.11) (2024-07-19) + + + + +### Bug Fixes: + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.10](https://github.com/ash-project/ash_postgres/compare/v2.1.9...v2.1.10) (2024-07-18) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index d3e46547..92e12e15 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.10" + @version "2.1.11" def project do [ From b3eea1e440e62f80d110bbfa415c476004cd189c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 13:28:39 -0400 Subject: [PATCH 0603/1215] chore: fix build --- lib/mix/tasks/ash_postgres.install.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index e58a80fe..bec3d024 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -58,7 +58,7 @@ defmodule Mix.Tasks.AshPostgres.Install do igniter |> Igniter.create_or_update_elixir_file("config/runtime.exs", default_runtime, fn zipper -> - if Igniter.Project.Config.configures?(zipper, [repo, :url], otp_app) do + if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo, :url]) do zipper else patterns = [ @@ -78,8 +78,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> - with {:ok, _zipper} <- - Igniter.Code.Function.move_to_function_call_in_current_scope( + case Igniter.Code.Function.move_to_function_call_in_current_scope( zipper, :=, 2, @@ -91,6 +90,7 @@ defmodule Mix.Tasks.AshPostgres.Install do ) end ) do + {:ok, zipper} -> zipper |> Igniter.Project.Config.modify_configuration_code( [repo, :url], @@ -106,7 +106,6 @@ defmodule Mix.Tasks.AshPostgres.Install do """) ) |> then(&{:ok, &1}) - else _ -> Igniter.Code.Common.add_code(zipper, """ database_url = From 5d9f2cd69378578353d97455eb6b3bf2d98143f0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 13:30:13 -0400 Subject: [PATCH 0604/1215] chore: remove debugging code --- lib/mix/tasks/ash_postgres.install.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index bec3d024..7aaae584 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -97,7 +97,6 @@ defmodule Mix.Tasks.AshPostgres.Install do otp_app, {:database_url, [], nil} ) - |> Igniter.Util.Debug.puts_code_at_node() |> Igniter.Project.Config.modify_configuration_code( [repo, :pool_size], otp_app, From 384344ab6bfabb5cb704ef7796ce6c7bdf694b49 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 15:11:44 -0400 Subject: [PATCH 0605/1215] fix: properly add prod config in installer --- lib/mix/tasks/ash_postgres.install.ex | 55 ++++++++++++++------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 7aaae584..48d490c9 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -78,33 +78,34 @@ defmodule Mix.Tasks.AshPostgres.Install do |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> - case Igniter.Code.Function.move_to_function_call_in_current_scope( - zipper, - :=, - 2, - fn call -> - Igniter.Code.Function.argument_matches_pattern?( - call, - 0, - {:database_url, _, ctx} when is_atom(ctx) - ) - end - ) do - {:ok, zipper} -> - zipper - |> Igniter.Project.Config.modify_configuration_code( - [repo, :url], - otp_app, - {:database_url, [], nil} - ) - |> Igniter.Project.Config.modify_configuration_code( - [repo, :pool_size], - otp_app, - Sourceror.parse_string!(""" - String.to_integer(System.get_env("POOL_SIZE") || "10") - """) - ) - |> then(&{:ok, &1}) + case Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :=, + 2, + fn call -> + Igniter.Code.Function.argument_matches_pattern?( + call, + 0, + {:database_url, _, ctx} when is_atom(ctx) + ) + end + ) do + {:ok, _zipper} -> + zipper + |> Igniter.Project.Config.modify_configuration_code( + [repo, :url], + otp_app, + {:database_url, [], nil} + ) + |> Igniter.Project.Config.modify_configuration_code( + [repo, :pool_size], + otp_app, + Sourceror.parse_string!(""" + String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + ) + |> then(&{:ok, &1}) + _ -> Igniter.Code.Common.add_code(zipper, """ database_url = From 01edc539fc45de89f1744362ca71c0d70846e3fe Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Jul 2024 15:12:46 -0400 Subject: [PATCH 0606/1215] chore: release version v2.1.12 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8ae08e..784a7b4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,43 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.12](https://github.com/ash-project/ash_postgres/compare/v2.1.11...v2.1.12) (2024-07-19) + + + + +### Bug Fixes: + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.11](https://github.com/ash-project/ash_postgres/compare/v2.1.10...v2.1.11) (2024-07-19) diff --git a/mix.exs b/mix.exs index 92e12e15..38934d69 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.11" + @version "2.1.12" def project do [ From ece56c2bc9e1951ed135728e221d1a4d3f7d9512 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Mon, 22 Jul 2024 16:29:00 +0200 Subject: [PATCH 0607/1215] test: add test for list aggregate in atomic validation (#358) --- test/atomics_test.exs | 20 ++++++++++++++++++++ test/support/resources/post.ex | 15 +++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index a0a34200..b57b5697 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -1,4 +1,5 @@ defmodule AshPostgres.AtomicsTest do + alias AshPostgres.Test.Comment alias AshPostgres.Test.Author use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post @@ -291,4 +292,23 @@ defmodule AshPostgres.AtomicsTest do |> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true) |> Map.get(:records) end + + test "can use list aggregate in validation" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{post_id: post.id, title: "foo"}) + |> Ash.create!() + + Logger.configure(level: :debug) + + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + post + |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) + |> Ash.destroy() + end + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e99bff53..ecd0a708 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -13,6 +13,17 @@ defmodule PassIfOriginalDataPresent do end end +defmodule HasNoComments do + alias Ash.Error.Invalid + use Ash.Resource.Validation + + def atomic(_changeset, _opts, _context) do + # This uses the list aggregate because we want to specifically test this aggregate + {:atomic, [], expr(list(comments, field: :id) > 0), + expr(error(^Invalid, %{message: "Can only delete if Post has no comments"}))} + end +end + defmodule AshPostgres.Test.Post do @moduledoc false use Ash.Resource, @@ -92,6 +103,10 @@ defmodule AshPostgres.Test.Post do change(filter(expr(title == "fred"))) end + destroy :destroy_if_no_comments do + validate(HasNoComments) + end + update :update_only_freds do change(filter(expr(title == "fred"))) end From a672c8969751be9539b7f907e0ec11aa9311bad6 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Mon, 22 Jul 2024 17:25:00 +0200 Subject: [PATCH 0608/1215] test: test more aggregates (#359) --- test/atomics_test.exs | 2 -- test/support/resources/post.ex | 12 +++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index b57b5697..418206ce 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -303,8 +303,6 @@ defmodule AshPostgres.AtomicsTest do |> Ash.Changeset.for_create(:create, %{post_id: post.id, title: "foo"}) |> Ash.create!() - Logger.configure(level: :debug) - assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> post |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index ecd0a708..a058bce4 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -18,9 +18,15 @@ defmodule HasNoComments do use Ash.Resource.Validation def atomic(_changeset, _opts, _context) do - # This uses the list aggregate because we want to specifically test this aggregate - {:atomic, [], expr(list(comments, field: :id) > 0), - expr(error(^Invalid, %{message: "Can only delete if Post has no comments"}))} + # Test multiple types of aggregates in a single validation + [ + {:atomic, [], + expr( + list(comments, field: :id) > 0 or + count(comments) > 0 or + exists(comments, true) + ), expr(error(^Invalid, %{message: "Can only delete if Post has no comments"}))} + ] end end From b00a7e64826657903846ceed120adb3a260a388f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Jul 2024 11:49:59 -0400 Subject: [PATCH 0609/1215] fix: update ash & ash_sql for fixes, test atomic alidations in destroys --- lib/data_layer.ex | 74 +++++++++++++++++++++++----------- mix.exs | 4 +- mix.lock | 13 +++--- test/atomics_test.exs | 5 ++- test/support/resources/post.ex | 11 +++-- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b0676b61..aeaff306 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2776,42 +2776,68 @@ defmodule AshPostgres.DataLayer do @impl true def destroy(resource, %{data: record} = changeset) do - ecto_changeset = ecto_changeset(record, changeset, :delete) - - try do - repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - - source = resolve_source(resource, changeset) + source = resolve_source(resource, changeset) + query = from(row in source, as: ^0) |> AshSql.Bindings.default_bindings( resource, AshPostgres.SqlImplementation, changeset.context ) - |> filter(changeset.filter, resource) - |> case do - {:ok, query} -> - query - |> pkey_filter(record) - |> repo.delete_all( - AshSql.repo_opts( - repo, - AshPostgres.SqlImplementation, - changeset.timeout, - changeset.tenant, - changeset.resource - ) - ) + |> pkey_filter(record) - :ok + with {:ok, query} <- filter(query, changeset.filter, resource) do + ecto_changeset = + case changeset.data do + %Ash.Changeset.OriginalDataNotAvailable{} -> + changeset.resource.__struct__() + + data -> + data + end + |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) + |> ecto_changeset(changeset, :delete, true) + case bulk_updatable_query( + query, + resource, + [], + [], + changeset.context, + :destroy + ) do {:error, error} -> {:error, error} + + {:ok, query} -> + try do + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + + repo_opts = + AshSql.repo_opts( + repo, + AshPostgres.SqlImplementation, + changeset.timeout, + changeset.tenant, + changeset.resource + ) + + query = Ecto.Query.exclude(query, :select) + + with_savepoint(repo, query, fn -> + repo.delete_all( + query, + repo_opts + ) + end) + + :ok + rescue + e -> + handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) + end end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) end end diff --git a/mix.exs b/mix.exs index 38934d69..bfe171b0 100644 --- a/mix.exs +++ b/mix.exs @@ -162,8 +162,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.1 and >= 3.2.1")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.24")}, + {:ash, ash_version("~> 3.1 and >= 3.2.5")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.25")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 1d361ab7..3e553de9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.2.2", "f079fbe7f4f7e3279825c841aa69f6b83333a86267b31435ca97031a805c4b41", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b9a9165d1aafec1fa719df6182ddee0b2a29c83f67079a04a9a8060f49cc1e7f"}, - "ash_sql": {:hex, :ash_sql, "0.2.24", "324f3fd65e249ad2e7df56a585a768edf6cc91246b7c8048ac16a684edc1437a", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "38f3066059be9f567ab5c3a8d99e812417069b881bc12fa33c067b1ea7742145"}, + "ash": {:hex, :ash, "3.2.5", "15702f44075cd34e0bd2756d1e286298c2c6b7b30990794940199885f43d8a44", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f7790ca7a77ec53d06f9b25820c5219e8ea0e7d05e3c477d783cf9d11428dc9a"}, + "ash_sql": {:hex, :ash_sql, "0.2.25", "a9489f5bd54a3a91e161f59b0c95385d62ca239fad6d4851011f532832f0f5ca", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "caeb8c5ea277aca8e9add5583bf9603bf7517c896a6e9d5eb4327a51f1209051"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -21,8 +21,9 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "igniter": {:hex, :igniter, "0.3.6", "30951d75604e88d6d893753e7331c9793625fbeb5fb34f06148e61ffcdca590f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "9fc32f2fc6eff9c24abe1d942a6632f5d50ccaeb031504064c1176f78f892b65"}, + "igniter": {:hex, :igniter, "0.3.8", "e6f423170e90a4547f3aca6b4e1879d742787bf7a577db9beee835c6b3890cc2", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "0850066244fead51ac7f749482889878db550c23c3c8addaf5b0d87956a44c91"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, @@ -31,14 +32,14 @@ "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "owl": {:hex, :owl, "0.9.0", "9b33d64734bd51d3fc1d6ed01b12f8c2ed23e1fbf8c43658a6dfbff62578bd03", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "cd70b55327985f8f24d38cb7de5bf8a8d24040e1b49cca2345508f8119ce81fd"}, + "owl": {:hex, :owl, "0.10.0", "1104390ee3e1b29e2c67c1539ffd7554d9f40e8ef67e4817098e9325684bed30", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "c16e35e00e8dd2bc23527678024e7f491064596d78ad40669d44992cac6afdff"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, - "reactor": {:hex, :reactor, "0.8.5", "7a621e0392a5975ed97938a4ddbbc92a6a31157fbd87446bc8bc6b1a0f49e56a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17b1976b9d333e55382dc108779078d5bbdbcd2c3d4033ea6dd52437339fe469"}, + "reactor": {:hex, :reactor, "0.9.0", "f48af9f300454b979a22d5a04b18b59e16959478ffa7f88d50b5e142b5d055dc", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c5ffd700ac669d0992a9e296978abe2110670b23addc0970fca9108d506489c"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, - "spark": {:hex, :spark, "2.2.9", "cc86e39895e1e1b2360e333fe37fa8cdb5624d265d234c0c945b1b1e11b49563", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "d855e3971f568527bdd43377a8088ae96a7798a1df732c9b7a631730ba2f705d"}, + "spark": {:hex, :spark, "2.2.10", "834c5f6c6874d019116096b82fe8a3e9bfe92077c3e06ead14b6daff528b69ef", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "78972edb0cc1539e56d42f08aabc88b8b2892d64e3e8bd44d58113b7f63622fa"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 418206ce..d1c25ecb 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -1,6 +1,7 @@ defmodule AshPostgres.AtomicsTest do - alias AshPostgres.Test.Comment alias AshPostgres.Test.Author + alias AshPostgres.Test.Comment + use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post @@ -306,7 +307,7 @@ defmodule AshPostgres.AtomicsTest do assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> post |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) - |> Ash.destroy() + |> Ash.destroy!() end end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index a058bce4..ca814737 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -14,7 +14,7 @@ defmodule PassIfOriginalDataPresent do end defmodule HasNoComments do - alias Ash.Error.Invalid + @moduledoc false use Ash.Resource.Validation def atomic(_changeset, _opts, _context) do @@ -22,10 +22,15 @@ defmodule HasNoComments do [ {:atomic, [], expr( - list(comments, field: :id) > 0 or + length(list(comments, field: :id)) > 0 or count(comments) > 0 or exists(comments, true) - ), expr(error(^Invalid, %{message: "Can only delete if Post has no comments"}))} + ), + expr( + error(^Ash.Error.Changes.InvalidChanges, %{ + message: "Can only delete if Post has no comments" + }) + )} ] end end From 8cd441a64664625d8efc7e64fa4bdf96b05a82bd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Jul 2024 11:50:22 -0400 Subject: [PATCH 0610/1215] chore: release version v2.1.13 --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784a7b4e..84aa475f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,45 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.13](https://github.com/ash-project/ash_postgres/compare/v2.1.12...v2.1.13) (2024-07-22) + + + + +### Bug Fixes: + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.12](https://github.com/ash-project/ash_postgres/compare/v2.1.11...v2.1.12) (2024-07-19) diff --git a/mix.exs b/mix.exs index bfe171b0..e7eabc3f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.12" + @version "2.1.13" def project do [ From 1b687b6c2a37634443af5712a3553c8b405719e4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Jul 2024 15:38:42 -0400 Subject: [PATCH 0611/1215] fix: properly convert tenant to string when building lateral join --- lib/data_layer.ex | 2 +- test/multitenancy_test.exs | 27 ++++++++++++++-------- test/support/multitenancy/resources/org.ex | 15 ++++++++++++ 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index aeaff306..e5f9aed6 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1271,7 +1271,7 @@ defmodule AshPostgres.DataLayer do query_tenant = case source_query do %{__tenant__: tenant} -> tenant - %{tenant: tenant} -> tenant + %{tenant: tenant} -> Ash.ToTenant.to_tenant(tenant, resource) _ -> nil end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 5d107f2b..bacd210a 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -30,10 +30,17 @@ defmodule AshPostgres.Test.MultitenancyTest do assert Enum.sort(AshPostgres.TestRepo.all_tenants()) == tenant_ids end + test "lateral joining attribute multitenancy to context multitenancy works", %{org1: org1} do + Org + |> Ash.Query.for_read(:read, %{}, tenant: org1) + |> Ash.Query.load(posts: Ash.Query.limit(Post, 2)) + |> Ash.read!() + end + test "attribute multitenancy works", %{org1: %{id: org_id} = org1} do assert [%{id: ^org_id}] = Org - |> Ash.Query.set_tenant(tenant(org1)) + |> Ash.Query.set_tenant(org1) |> Ash.read!() end @@ -46,7 +53,7 @@ defmodule AshPostgres.Test.MultitenancyTest do assert [] = Org - |> Ash.Query.set_tenant(tenant(org1)) + |> Ash.Query.set_tenant(org1) |> Ash.Query.for_read(:has_policies, %{}, actor: user, authorize?: true) |> Ash.read!() end @@ -54,11 +61,11 @@ defmodule AshPostgres.Test.MultitenancyTest do test "context multitenancy works with policies", %{org1: org1} do post = Post - |> Ash.Changeset.for_create(:create, %{name: "foo"}, tenant: tenant(org1)) + |> Ash.Changeset.for_create(:create, %{name: "foo"}, tenant: org1) |> Ash.create!() post - |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true, tenant: tenant(org1)) + |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true, tenant: org1) |> Ash.update!() end @@ -77,11 +84,11 @@ defmodule AshPostgres.Test.MultitenancyTest do test "schema multitenancy works", %{org1: org1, org2: org2} do Post |> Ash.Changeset.for_create(:create, %{name: "foo"}) - |> Ash.Changeset.set_tenant(tenant(org1)) + |> Ash.Changeset.set_tenant(org1) |> Ash.create!() - assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Ash.read!() - assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Ash.read!() + assert [_] = Post |> Ash.Query.set_tenant(org1) |> Ash.read!() + assert [] = Post |> Ash.Query.set_tenant(org2) |> Ash.read!() end test "schema rename on update works", %{org1: org1} do @@ -198,7 +205,7 @@ defmodule AshPostgres.Test.MultitenancyTest do user = User |> Ash.Changeset.new() |> Ash.create!() Post - |> Ash.Changeset.for_create(:create, %{}, tenant: tenant(org)) + |> Ash.Changeset.for_create(:create, %{}, tenant: org) |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) |> Ash.create!() end @@ -247,7 +254,7 @@ defmodule AshPostgres.Test.MultitenancyTest do post = Post |> Ash.Changeset.for_create(:create, %{}) - |> Ash.Changeset.set_tenant(tenant(org1)) + |> Ash.Changeset.set_tenant(org1) |> Ash.create!() assert_raise Ash.Error.Invalid, @@ -255,7 +262,7 @@ defmodule AshPostgres.Test.MultitenancyTest do fn -> Post |> Ash.Changeset.for_create(:create, %{id: post.id}) - |> Ash.Changeset.set_tenant(tenant(org1)) + |> Ash.Changeset.set_tenant(org1) |> Ash.create!() end end diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index 3d1353c8..15c24e05 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -5,6 +5,17 @@ defmodule AshPostgres.MultitenancyTest.Org do data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer] + defimpl Ash.ToTenant do + def to_tenant(%{id: id}, resource) do + if Ash.Resource.Info.data_layer(resource) == AshPostgres.DataLayer && + Ash.Resource.Info.multitenancy_strategy(resource) == :context do + "org_#{id}" + else + id + end + end + end + policies do policy action(:has_policies) do authorize_if(relates_to_actor_via(:owner)) @@ -68,4 +79,8 @@ defmodule AshPostgres.MultitenancyTest.Org do def tenant("org_" <> tenant) do tenant end + + def tenant(tenant) do + tenant + end end From 605703c2e57837405c019221f95277cda7c2eaa3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 22 Jul 2024 15:41:55 -0400 Subject: [PATCH 0612/1215] chore: release version v2.1.14 --- CHANGELOG.md | 101 +++++---------------------------------------------- mix.exs | 2 +- 2 files changed, 11 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84aa475f..784f964c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,116 +5,35 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline -## [v2.1.13](https://github.com/ash-project/ash_postgres/compare/v2.1.12...v2.1.13) (2024-07-22) - - - +## [v2.1.14](https://github.com/ash-project/ash_postgres/compare/v2.1.13...v2.1.14) (2024-07-22) ### Bug Fixes: -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) +- [multitenancy] properly convert tenant to string when building lateral join -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - -### Improvements: - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression +## [v2.1.13](https://github.com/ash-project/ash_postgres/compare/v2.1.12...v2.1.13) (2024-07-22) -* use latest type casting code from ash +### Bug Fixes: -* support new type determination code +- [atomic validations] update ash & ash_sql for fixes, test atomic validations in destroys ## [v2.1.12](https://github.com/ash-project/ash_postgres/compare/v2.1.11...v2.1.12) (2024-07-19) - - - ### Bug Fixes: -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - -### Improvements: - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code - -## [v2.1.11](https://github.com/ash-project/ash_postgres/compare/v2.1.10...v2.1.11) (2024-07-19) - - - +- [`mix ash_postgres.install`] properly add prod config in installer ### Bug Fixes: -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix +- [`mix ash_postgres.install`] properly perform or don't perform configuration modification code -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set +- [`has_many` relationships] allow non-unique has_many source_attributes (#355) ### Improvements: -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash +- [`mix ash_postgres.install`] prepend `:postgres` to section order -* support new type determination code +- [`mix ash.patch.extend`] pluralize table name in extender ## [v2.1.10](https://github.com/ash-project/ash_postgres/compare/v2.1.9...v2.1.10) (2024-07-18) diff --git a/mix.exs b/mix.exs index e7eabc3f..72e603d4 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.13" + @version "2.1.14" def project do [ From 0a6f8909078c78e906efbce6b1177a5c0becf7de Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 23 Jul 2024 09:39:18 +0200 Subject: [PATCH 0613/1215] add update case to validation test --- test/atomics_test.exs | 10 +++++++++- test/support/resources/post.ex | 10 +++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index d1c25ecb..97025cb6 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -294,7 +294,7 @@ defmodule AshPostgres.AtomicsTest do |> Map.get(:records) end - test "can use list aggregate in validation" do + test "can use aggregates in validation" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) @@ -304,6 +304,14 @@ defmodule AshPostgres.AtomicsTest do |> Ash.Changeset.for_create(:create, %{post_id: post.id, title: "foo"}) |> Ash.create!() + Logger.configure(level: :debug) + + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + post + |> Ash.Changeset.for_update(:update_if_no_comments, %{title: "bar"}) + |> Ash.update!() + end + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> post |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index ca814737..79299dfd 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -22,9 +22,9 @@ defmodule HasNoComments do [ {:atomic, [], expr( - length(list(comments, field: :id)) > 0 or - count(comments) > 0 or - exists(comments, true) + # length(list(comments, field: :id)) > 0 or + # count(comments) > 0 or + exists(comments, true) ), expr( error(^Ash.Error.Changes.InvalidChanges, %{ @@ -118,6 +118,10 @@ defmodule AshPostgres.Test.Post do validate(HasNoComments) end + update :update_if_no_comments do + validate(HasNoComments) + end + update :update_only_freds do change(filter(expr(title == "fred"))) end From 9e4efa7b9bdbb9b73e033b83c007bfb280203eb8 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 23 Jul 2024 10:00:52 +0200 Subject: [PATCH 0614/1215] remove Logger config and enable aggregates in validation again --- test/atomics_test.exs | 2 -- test/support/resources/post.ex | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 97025cb6..48d77514 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -304,8 +304,6 @@ defmodule AshPostgres.AtomicsTest do |> Ash.Changeset.for_create(:create, %{post_id: post.id, title: "foo"}) |> Ash.create!() - Logger.configure(level: :debug) - assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> post |> Ash.Changeset.for_update(:update_if_no_comments, %{title: "bar"}) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 79299dfd..3dbd324d 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -22,9 +22,9 @@ defmodule HasNoComments do [ {:atomic, [], expr( - # length(list(comments, field: :id)) > 0 or - # count(comments) > 0 or - exists(comments, true) + length(list(comments, field: :id)) > 0 or + count(comments) > 0 or + exists(comments, true) ), expr( error(^Ash.Error.Changes.InvalidChanges, %{ From 306dc84ac27b42ae7b6797a75689662ef890ffeb Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 23 Jul 2024 12:55:44 +0200 Subject: [PATCH 0615/1215] test validation aggregates individually as well (#360) * test aggregates individually as well * test with atomic and non atomic actions * test atomic and non atomic actions * use message in context --- test/atomics_test.exs | 72 ++++++++++++++++++++++++---------- test/support/resources/post.ex | 54 ++++++++++++++++++++----- 2 files changed, 95 insertions(+), 31 deletions(-) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 48d77514..4ad038d9 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -294,26 +294,56 @@ defmodule AshPostgres.AtomicsTest do |> Map.get(:records) end - test "can use aggregates in validation" do - post = - Post - |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) - |> Ash.create!() - - Comment - |> Ash.Changeset.for_create(:create, %{post_id: post.id, title: "foo"}) - |> Ash.create!() - - assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> - post - |> Ash.Changeset.for_update(:update_if_no_comments, %{title: "bar"}) - |> Ash.update!() - end - - assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> - post - |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) - |> Ash.destroy!() + Enum.each( + [ + :exists, + :list, + :count, + :combined + ], + fn aggregate -> + test "can use #{aggregate} in validation" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{post_id: post.id, title: "foo"}) + |> Ash.create!() + + assert_raise Ash.Error.Invalid, ~r/Can only update if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_update(:update_if_no_comments, %{title: "bar"}) + |> Ash.update!() + end + + assert_raise Ash.Error.Invalid, ~r/Can only update if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_update(:update_if_no_comments_non_atomic, %{title: "bar"}) + |> Ash.update!() + end + + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_destroy(:destroy_if_no_comments_non_atomic, %{}) + |> Ash.destroy!() + end + + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) + |> Ash.destroy!() + end + end end - end + ) end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 3dbd324d..473a0103 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -17,18 +17,32 @@ defmodule HasNoComments do @moduledoc false use Ash.Resource.Validation - def atomic(_changeset, _opts, _context) do + def atomic(changeset, _opts, context) do # Test multiple types of aggregates in a single validation + condition = + case changeset.context.aggregate do + :exists -> + expr(exists(comments, true)) + + :list -> + expr(length(list(comments, field: :id)) > 0) + + :count -> + expr(count(comments) > 0) + + :combined -> + expr( + exists(comments, true) and + length(list(comments, field: :id)) > 0 and + count(comments) > 0 + ) + end + [ - {:atomic, [], - expr( - length(list(comments, field: :id)) > 0 or - count(comments) > 0 or - exists(comments, true) - ), + {:atomic, [], condition, expr( error(^Ash.Error.Changes.InvalidChanges, %{ - message: "Can only delete if Post has no comments" + message: ^context.message || "Post has comments" }) )} ] @@ -115,11 +129,31 @@ defmodule AshPostgres.Test.Post do end destroy :destroy_if_no_comments do - validate(HasNoComments) + validate HasNoComments do + message "Can only delete if Post has no comments" + end end update :update_if_no_comments do - validate(HasNoComments) + validate HasNoComments do + message "Can only update if Post has no comments" + end + end + + destroy :destroy_if_no_comments_non_atomic do + require_atomic?(false) + + validate HasNoComments do + message "Can only delete if Post has no comments" + end + end + + update :update_if_no_comments_non_atomic do + require_atomic?(false) + + validate HasNoComments do + message "Can only update if Post has no comments" + end end update :update_only_freds do From cb397e9387930e4ee2f0ffc884c7d537d6ac014c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 23 Jul 2024 07:57:57 -0400 Subject: [PATCH 0616/1215] fix: use a subquery if any exists aggregates are in play improvement: update ash_sql dependencies for bug fixes --- lib/data_layer.ex | 114 ++++++++++++++++------------------------------ mix.exs | 2 +- mix.lock | 6 +-- 3 files changed, 42 insertions(+), 80 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e5f9aed6..36a54834 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1465,9 +1465,20 @@ defmodule AshPostgres.DataLayer do end) end + has_exists? = + Enum.any?(atomics, fn {_key, expr} -> + Ash.Filter.find(expr, fn + %Ash.Query.Exists{} -> + true + + _ -> + false + end) + end) + needs_to_join? = requires_adding_inner_join? || - query.limit || query.offset + query.limit || query.offset || has_exists? query = if needs_to_join? do @@ -1481,7 +1492,7 @@ defmodule AshPostgres.DataLayer do {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} end - !Enum.empty?(query.joins) -> + !Enum.empty?(query.joins) || has_exists? -> with root_query <- Ecto.Query.exclude(root_query, :order_by), {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do @@ -2011,7 +2022,7 @@ defmodule AshPostgres.DataLayer do ) end - defp ecto_changeset(record, changeset, type, table_error? \\ true) do + defp ecto_changeset(record, changeset, type, table_error?) do attributes = changeset.resource |> Ash.Resource.Info.attributes() @@ -2673,11 +2684,6 @@ defmodule AshPostgres.DataLayer do @impl true def update(resource, changeset) do - ecto_changeset = - changeset.data - |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :update) - source = resolve_source(resource, changeset) query = @@ -2689,78 +2695,34 @@ defmodule AshPostgres.DataLayer do ) |> pkey_filter(changeset.data) - case bulk_updatable_query(query, resource, changeset.atomics, [], changeset.context) do - {:error, error} -> - {:error, error} - - {:ok, query} -> - modifying = - Map.keys(changeset.attributes) ++ - Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) - - query = Ecto.Query.select(query, ^modifying) - - try do - case AshSql.Atomics.query_with_atomics( - resource, - query, - changeset.filter, - changeset.atomics, - ecto_changeset.changes, - [] - ) do - :empty -> - {:ok, changeset.data} - - {:ok, query} -> - repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - - repo_opts = - AshSql.repo_opts( - repo, - AshPostgres.SqlImplementation, - changeset.timeout, - changeset.tenant, - changeset.resource - ) - - repo_opts = - Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) - - result = - with_savepoint(repo, query, fn -> - repo.update_all( - query, - [], - repo_opts - ) - end) - - case result do - {0, []} -> - {:error, - Ash.Error.Changes.StaleRecord.exception( - resource: resource, - filter: changeset.filter - )} + changeset = + Ash.Changeset.set_context(changeset, %{ + data_layer: %{ + use_atomic_update_data?: true + } + }) + + case update_query(query, changeset, resource, %{ + return_records?: true, + calculations: [] + }) do + {:ok, []} -> + {:error, + Ash.Error.Changes.StaleRecord.exception( + resource: resource, + filter: changeset.filter + )} - {1, [result]} -> - record = - changeset.data - |> Map.merge(Map.take(result, modifying)) + {:ok, [record]} -> + maybe_update_tenant(resource, changeset, record) - maybe_update_tenant(resource, changeset, record) + {:ok, record} - {:ok, record} - end + {:error, error} -> + {:error, error} - {:error, error} -> - {:error, error} - end - rescue - e -> - handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) - end + {:error, :no_rollback, error} -> + {:error, :no_rollback, error} end end diff --git a/mix.exs b/mix.exs index 72e603d4..32b724c9 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.2.5")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.25")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.26")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 3e553de9..b0c30357 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.2.5", "15702f44075cd34e0bd2756d1e286298c2c6b7b30990794940199885f43d8a44", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f7790ca7a77ec53d06f9b25820c5219e8ea0e7d05e3c477d783cf9d11428dc9a"}, - "ash_sql": {:hex, :ash_sql, "0.2.25", "a9489f5bd54a3a91e161f59b0c95385d62ca239fad6d4851011f532832f0f5ca", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "caeb8c5ea277aca8e9add5583bf9603bf7517c896a6e9d5eb4327a51f1209051"}, + "ash": {:hex, :ash, "3.2.6", "a09042b76c5b573831fcdf004d1109592f9b63fdebc5b464a750289cfaecad83", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee850c1e77f987ac094199175610e86558087469a930762e3cd8f6d91ef9da8d"}, + "ash_sql": {:hex, :ash_sql, "0.2.26", "79158f0ec945c83a4403eaa75f7349dde573c4115ef477f57c22624f27607f21", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a19b591df382cbc30131e5724514b3737c10a2ab7baf497f5229d1b3557d1244"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "igniter": {:hex, :igniter, "0.3.8", "e6f423170e90a4547f3aca6b4e1879d742787bf7a577db9beee835c6b3890cc2", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "0850066244fead51ac7f749482889878db550c23c3c8addaf5b0d87956a44c91"}, + "igniter": {:hex, :igniter, "0.3.9", "2a3c80e3d5a0f3758670eaa7658fe6334633dab3fd9bca9aae69802f8282a0b3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "2a5b8618a0aef8e5a545d05d389ba20fc5b0b4b8a6c45cf4f900890c263c7fdc"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, From de2d344de710144094f86228009886b4d495aa2e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 23 Jul 2024 08:11:00 -0400 Subject: [PATCH 0617/1215] chore: release version v2.1.15 --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784f964c..53d37a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,51 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.15](https://github.com/ash-project/ash_postgres/compare/v2.1.14...v2.1.15) (2024-07-23) + + + + +### Bug Fixes: + +* use a subquery if any exists aggregates are in play + +* properly convert tenant to string when building lateral join + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* update ash_sql dependencies for bug fixes + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.14](https://github.com/ash-project/ash_postgres/compare/v2.1.13...v2.1.14) (2024-07-22) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 32b724c9..789b8fb5 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.14" + @version "2.1.15" def project do [ From e49b678351495d0b6b9e50252912b5247092d8d1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 24 Jul 2024 17:22:14 -0400 Subject: [PATCH 0618/1215] fix: ensure app is compiled before using repo modules --- lib/mix/helpers.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index dc0918a3..9168165b 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -85,6 +85,13 @@ defmodule AshPostgres.Mix.Helpers do repos end else + if Code.ensure_loaded?(Mix.Tasks.App.Config) do + Mix.Task.run("app.config", args) + else + Mix.Task.run("loadpaths", args) + "--no-compile" not in args && Mix.Task.run("compile", args) + end + Mix.Project.config()[:app] |> Application.get_env(:ecto_repos, []) |> Enum.filter(fn repo -> From 2b4befedf117e00d83882641880b98291e26b770 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 25 Jul 2024 07:02:12 -0400 Subject: [PATCH 0619/1215] chore: add `igniter.install` version of getting started guide --- .../get-started-with-ash-postgres.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index 5897b4ff..db8e1c15 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -1,27 +1,22 @@ # Get Started With Postgres -## Goals +## Installation -In this guide we will: +We recommend [reading up on postgresql](https://www.postgresql.org/docs/16/index.html) if you haven't. -1. Setup AshPostgres, which includes setting up [Ecto](https://hexdocs.pm/ecto/Ecto.html) -2. Add AshPostgres to the resources created in [the Ash getting started guide](https://hexdocs.pm/ash/get-started.html) -3. Show how the various features of AshPostgres can help you work quickly and cleanly against a postgres database -4. Highlight some of the more advanced features you can use when using AshPostgres. -5. Point you to additional resources you may need on your journey +- [Postgres must be installed](https://www.postgresql.org/download/) with a sufficiently permissive user -## Things you may want to read + -- [Install PostgreSQL](https://www.postgresql.org/download/) (I recommend the homebrew option for mac users) +### Using Igniter (recommended) -## Requirements - -- A working Postgres installation, with a sufficiently permissive user -- If you would like to follow along, you will need to add begin with [the Ash getting started guide](https://hexdocs.pm/ash/get-started.html) +```sh +mix igniter.install ash_postgres +``` -## Steps +### Manually -### Add AshPostgres +#### Add AshPostgres Add the `:ash_postgres` dependency to your application @@ -37,7 +32,7 @@ Add `:ash_postgres` to your `.formatter.exs` file ] ``` -### Create and configure your Repo +#### Create and configure your Repo Create `lib/helpdesk/repo.ex` with the following contents. `AshPostgres.Repo` is a thin wrapper around `Ecto.Repo`, so see their documentation for how to use it if you need to use it directly. For standard Ash usage, all you will need to do is configure your resources to use your repo. @@ -153,7 +148,7 @@ And finally, add the repo to your application ... ``` -### Add AshPostgres to our resources +#### Add AshPostgres to our resources Now we can add the data layer to our resources. The basic configuration for a resource requires the `d:AshPostgres.postgres|table` and the `d:AshPostgres.postgres|repo`. @@ -183,7 +178,7 @@ Now we can add the data layer to our resources. The basic configuration for a re end ``` -### Create the database and tables +#### Create the database and tables First, we'll create the database with `mix ash.setup`. @@ -205,8 +200,13 @@ Finally, we will create the local database and apply the generated migrations: mix ash.setup ``` + + ### Try it out +This is based on the [Getting Started](https://hexdocs.pm/ash/getting_started.html) guide. +If you haven't already, you should read that first. + And now we're ready to try it out! Run the following in iex: Lets create some data. We'll make a representative and give them some open and some closed tickets. From be7329d21ac841dd3c4c308918a14df9b8838095 Mon Sep 17 00:00:00 2001 From: Robert Timis <65460527+TimisRobert@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:46:14 +0200 Subject: [PATCH 0620/1215] docs: Update migrations-and-tasks.md (#363) Add missing filter to account for resources without repo --- documentation/topics/development/migrations-and-tasks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 1165569a..619dded2 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -129,6 +129,7 @@ Tasks that need to be executed in the released application (because mix is not p domain |> Ash.Domain.Info.resources() |> Enum.map(&AshPostgres.DataLayer.Info.repo/1) + |> Enum.reject(&is_nil/1) end) |> Enum.uniq() end From ac08104b25ea1e8189fcbe5a25086b74ab757676 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 25 Jul 2024 16:15:23 -0400 Subject: [PATCH 0621/1215] fix: don't overwrite non-updated fields on update improvement: update ash_sql for cleaner queries --- lib/data_layer.ex | 7 +++++++ mix.exs | 2 +- mix.lock | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 36a54834..798b4a47 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2685,6 +2685,9 @@ defmodule AshPostgres.DataLayer do @impl true def update(resource, changeset) do source = resolve_source(resource, changeset) + modifying = + Map.keys(changeset.attributes) ++ + Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) query = from(row in source, as: ^0) @@ -2714,6 +2717,10 @@ defmodule AshPostgres.DataLayer do )} {:ok, [record]} -> + record = + changeset.data + |> Map.merge(Map.take(record, modifying)) + maybe_update_tenant(resource, changeset, record) {:ok, record} diff --git a/mix.exs b/mix.exs index 789b8fb5..666cc66f 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.1 and >= 3.2.5")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.26")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.27")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index b0c30357..0ece96bf 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.2.6", "a09042b76c5b573831fcdf004d1109592f9b63fdebc5b464a750289cfaecad83", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee850c1e77f987ac094199175610e86558087469a930762e3cd8f6d91ef9da8d"}, - "ash_sql": {:hex, :ash_sql, "0.2.26", "79158f0ec945c83a4403eaa75f7349dde573c4115ef477f57c22624f27607f21", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a19b591df382cbc30131e5724514b3737c10a2ab7baf497f5229d1b3557d1244"}, + "ash_sql": {:hex, :ash_sql, "0.2.27", "57e3dedf749d80f170c424cfccadc58e2a069831c560334aa57e6b5196094a5c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "826c4da87157bbdc7e8149576f4b6aecfc6b8481e2b893f7e85747ce594f24e1"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -32,13 +32,13 @@ "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "owl": {:hex, :owl, "0.10.0", "1104390ee3e1b29e2c67c1539ffd7554d9f40e8ef67e4817098e9325684bed30", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "c16e35e00e8dd2bc23527678024e7f491064596d78ad40669d44992cac6afdff"}, + "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "reactor": {:hex, :reactor, "0.9.0", "f48af9f300454b979a22d5a04b18b59e16959478ffa7f88d50b5e142b5d055dc", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c5ffd700ac669d0992a9e296978abe2110670b23addc0970fca9108d506489c"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.4.0", "be87319b1579191e25464005d465713079b3fd7124a3938a1e6cf4def39735a9", [:mix], [], "hexpm", "16751ca55e3895f2228938b703ad399b0b27acfe288eff6c0e629ed3e6ec0358"}, + "sourceror": {:hex, :sourceror, "1.5.0", "3e65d5fbb1a8e2864ad6411262c8018fee73474f5789dda12285c82999253d5d", [:mix], [], "hexpm", "4a32b5d189d8453f73278c15712f8731b89e9211e50726b798214b303b51bfc7"}, "spark": {:hex, :spark, "2.2.10", "834c5f6c6874d019116096b82fe8a3e9bfe92077c3e06ead14b6daff528b69ef", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "78972edb0cc1539e56d42f08aabc88b8b2892d64e3e8bd44d58113b7f63622fa"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, From 07d84d4034f6b6c52495971ea67ab4ecd15901fe Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 25 Jul 2024 16:17:08 -0400 Subject: [PATCH 0622/1215] chore: release version v2.1.16 --- CHANGELOG.md | 41 +++++++---------------------------------- mix.exs | 2 +- 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d37a7e..f8749284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,50 +5,23 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline -## [v2.1.15](https://github.com/ash-project/ash_postgres/compare/v2.1.14...v2.1.15) (2024-07-23) - - - +## [v2.1.16](https://github.com/ash-project/ash_postgres/compare/v2.1.15...v2.1.16) (2024-07-25) ### Bug Fixes: -* use a subquery if any exists aggregates are in play - -* properly convert tenant to string when building lateral join - -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) +- [updates] don't overwrite non-updated fields on update -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set +- [`mix ash_postgres.generate_migrations`] ensure app is compiled before using repo modules ### Improvements: -* update ash_sql dependencies for bug fixes - -* prepend `:postgres` to section order +- [`ash_sql`] update ash_sql for cleaner queries -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression +## [v2.1.15](https://github.com/ash-project/ash_postgres/compare/v2.1.14...v2.1.15) (2024-07-23) -* use latest type casting code from ash +### Bug Fixes: -* support new type determination code +- [query building] use a subquery if any exists aggregates are in play ## [v2.1.14](https://github.com/ash-project/ash_postgres/compare/v2.1.13...v2.1.14) (2024-07-22) diff --git a/mix.exs b/mix.exs index 666cc66f..bd2b7026 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.15" + @version "2.1.16" def project do [ From 793d27ddd180fab8b185dc0a63dcebf57a593c80 Mon Sep 17 00:00:00 2001 From: Robert Timis <65460527+TimisRobert@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:27:02 +0200 Subject: [PATCH 0623/1215] test: replicate type mismatch bug (#364) --- .../user_invites/20240727145758.json | 49 ++++++++ .../test_repo/users/20240727145758.json | 117 ++++++++++++++++++ .../20240727145758_user_invites.exs | 29 +++++ test/atomics_test.exs | 21 ++++ test/support/domain.ex | 1 + test/support/resources/invite.ex | 23 ++++ test/support/resources/role.ex | 5 + test/support/resources/user.ex | 11 ++ 8 files changed, 256 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/user_invites/20240727145758.json create mode 100644 priv/resource_snapshots/test_repo/users/20240727145758.json create mode 100644 priv/test_repo/migrations/20240727145758_user_invites.exs create mode 100644 test/support/resources/invite.ex create mode 100644 test/support/resources/role.ex diff --git a/priv/resource_snapshots/test_repo/user_invites/20240727145758.json b/priv/resource_snapshots/test_repo/user_invites/20240727145758.json new file mode 100644 index 00000000..7373a8d8 --- /dev/null +++ b/priv/resource_snapshots/test_repo/user_invites/20240727145758.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "E2260A6737875A739EB4AFA8896BE4E95F3DC5A624E701AA5F1D09D68C315BBD", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "user_invites" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/users/20240727145758.json b/priv/resource_snapshots/test_repo/users/20240727145758.json new file mode 100644 index 00000000..54b6b916 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20240727145758.json @@ -0,0 +1,117 @@ +{ + "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": "is_active", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "\"user\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "users_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "name": "users_org_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_orgs" + }, + "size": null, + "source": "org_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "80A9C5429DDFAB41DA8448FEC86BEDEEA7CB6A53B69E9A03BD938098A55D6815", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "users" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240727145758_user_invites.exs b/priv/test_repo/migrations/20240727145758_user_invites.exs new file mode 100644 index 00000000..abfce2fb --- /dev/null +++ b/priv/test_repo/migrations/20240727145758_user_invites.exs @@ -0,0 +1,29 @@ +defmodule AshPostgres.TestRepo.Migrations.UserInvites 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(:users) do + add(:role, :text, default: "user") + end + + create table(:user_invites, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text, null: false) + add(:role, :text, null: false) + end + end + + def down do + drop(table(:user_invites)) + + alter table(:users) do + remove(:role) + end + end +end diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 4ad038d9..2ab42b6c 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -3,7 +3,9 @@ defmodule AshPostgres.AtomicsTest do alias AshPostgres.Test.Comment use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Invite alias AshPostgres.Test.Post + alias AshPostgres.Test.User import Ash.Expr require Ash.Query @@ -37,6 +39,25 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "a basic atomic works with enum/allow_nil? false" do + user = + User + |> Ash.Changeset.for_create(:create, %{name: "Dude", role: :user}) + |> Ash.create!() + + Invite + |> Ash.Changeset.for_create(:create, %{ + name: "Dude", + role: :admin + }) + |> Ash.create!() + + assert %{role: :admin} = + user + |> Ash.Changeset.for_update(:accept_invite, %{}) + |> Ash.update!() + end + test "atomics work with maps that contain lists" do post = Post diff --git a/test/support/domain.ex b/test/support/domain.ex index e322c204..a1037a47 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -12,6 +12,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Author) resource(AshPostgres.Test.Profile) resource(AshPostgres.Test.User) + resource(AshPostgres.Test.Invite) resource(AshPostgres.Test.Account) resource(AshPostgres.Test.Organization) resource(AshPostgres.Test.Manager) diff --git a/test/support/resources/invite.ex b/test/support/resources/invite.ex new file mode 100644 index 00000000..32406d1f --- /dev/null +++ b/test/support/resources/invite.ex @@ -0,0 +1,23 @@ +defmodule AshPostgres.Test.Invite do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, allow_nil?: false, public?: true) + attribute(:role, AshPostgres.Test.Role, allow_nil?: false, public?: true) + end + + postgres do + table "user_invites" + repo(AshPostgres.TestRepo) + end +end diff --git a/test/support/resources/role.ex b/test/support/resources/role.ex new file mode 100644 index 00000000..5ce35a73 --- /dev/null +++ b/test/support/resources/role.ex @@ -0,0 +1,5 @@ +defmodule AshPostgres.Test.Role do + @moduledoc false + + use Ash.Type.Enum, values: [:admin, :user] +end diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index bc57fe4f..25a99f18 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -9,6 +9,11 @@ defmodule AshPostgres.Test.User do defaults([:create, :read, :update, :destroy]) + update :accept_invite do + require_atomic?(false) + change(atomic_update(:role, expr(invite.role))) + end + read :active do filter(expr(active)) @@ -37,6 +42,7 @@ defmodule AshPostgres.Test.User do uuid_primary_key(:id) attribute(:is_active, :boolean, public?: true) attribute(:name, :string, public?: true) + attribute(:role, AshPostgres.Test.Role, allow_nil?: false, default: :user, public?: true) end postgres do @@ -53,5 +59,10 @@ defmodule AshPostgres.Test.User do has_many(:accounts, AshPostgres.Test.Account) do public?(true) end + + has_one(:invite, AshPostgres.Test.Invite) do + source_attribute(:name) + destination_attribute(:name) + end end end From fb5e57cccfb445e7a69993d4a81372ae949bd73d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Jul 2024 17:06:58 -0400 Subject: [PATCH 0624/1215] improvement: update ash & ash_sql for various fixes --- mix.exs | 4 ++-- mix.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mix.exs b/mix.exs index bd2b7026..6f610c6d 100644 --- a/mix.exs +++ b/mix.exs @@ -162,8 +162,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.1 and >= 3.2.5")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.27")}, + {:ash, ash_version("~> 3.3")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.28")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 0ece96bf..7e1e5c3d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.2.6", "a09042b76c5b573831fcdf004d1109592f9b63fdebc5b464a750289cfaecad83", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.2.12 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee850c1e77f987ac094199175610e86558087469a930762e3cd8f6d91ef9da8d"}, - "ash_sql": {:hex, :ash_sql, "0.2.27", "57e3dedf749d80f170c424cfccadc58e2a069831c560334aa57e6b5196094a5c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "826c4da87157bbdc7e8149576f4b6aecfc6b8481e2b893f7e85747ce594f24e1"}, + "ash": {:hex, :ash, "3.3.0", "0391883fe544beb354c6fc4d15a6c693e90aed0336ee1b9051da65ddc8134f6e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f1f49225a620cd9c3f0acfcbe24e99d6f8227a312009dbe3889295c4ef1bb869"}, + "ash_sql": {:hex, :ash_sql, "0.2.28", "633ad465e1c4dd1246d2e75b82b5cbdc9abab4fa897094f6de08bf21517bb664", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b076f825b81beaaa0a6e90257afbd0173f34690ce936080d72b2d79224a3609e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -20,11 +20,11 @@ "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, - "glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"}, - "igniter": {:hex, :igniter, "0.3.9", "2a3c80e3d5a0f3758670eaa7658fe6334633dab3fd9bca9aae69802f8282a0b3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "2a5b8618a0aef8e5a545d05d389ba20fc5b0b4b8a6c45cf4f900890c263c7fdc"}, + "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, + "igniter": {:hex, :igniter, "0.3.11", "1039c38eaf4e3c677712d097e18be96231479c3e9d7d8fd9c04397b1f6d3601d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "1381104faaa5c51b2cc540f185e72d3bbab0ea3eb57348c50af4440c7e469491"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, - "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, From b84713cca765f2358634978b4ce81fe0ad951d72 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Jul 2024 17:13:49 -0400 Subject: [PATCH 0625/1215] chore: release version v2.1.17 --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8749284..a7a67b47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,59 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.17](https://github.com/ash-project/ash_postgres/compare/v2.1.16...v2.1.17) (2024-07-27) + + + + +### Bug Fixes: + +* don't overwrite non-updated fields on update + +* ensure app is compiled before using repo modules + +* use a subquery if any exists aggregates are in play + +* properly convert tenant to string when building lateral join + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* update ash & ash_sql for various fixes + +* update ash_sql for cleaner queries + +* update ash_sql dependencies for bug fixes + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.16](https://github.com/ash-project/ash_postgres/compare/v2.1.15...v2.1.16) (2024-07-25) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 6f610c6d..28f8ed43 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.16" + @version "2.1.17" def project do [ From cba3966569a4b04e170828cc59e53ed34a7a7b0e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 30 Jul 2024 17:15:39 -0400 Subject: [PATCH 0626/1215] improvement: update ash_sql for latest fixes test: add test for exists w/ from_many? relationship --- mix.exs | 2 +- mix.lock | 8 ++++---- test/filter_test.exs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 28f8ed43..917cc54b 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.3")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.28")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.29")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 7e1e5c3d..3108ce27 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.3.0", "0391883fe544beb354c6fc4d15a6c693e90aed0336ee1b9051da65ddc8134f6e", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f1f49225a620cd9c3f0acfcbe24e99d6f8227a312009dbe3889295c4ef1bb869"}, - "ash_sql": {:hex, :ash_sql, "0.2.28", "633ad465e1c4dd1246d2e75b82b5cbdc9abab4fa897094f6de08bf21517bb664", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b076f825b81beaaa0a6e90257afbd0173f34690ce936080d72b2d79224a3609e"}, + "ash": {:hex, :ash, "3.3.1", "fc67719590b3f3488f90b267666364f6ac364e7658bee3806c2739c9850d05d9", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de8568f528194edd6d22f8941f5f67589788fe9a3868e900efac81e2ded25955"}, + "ash_sql": {:hex, :ash_sql, "0.2.29", "d99a40818667d1843e61edae6b3eeeaedda62f34e6477e4dd29017f81fddbf07", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1d3cc1c3a26cf87bb913fdf7bcbe9d901d35f260aac36225b8054541836e5752"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.11", "1039c38eaf4e3c677712d097e18be96231479c3e9d7d8fd9c04397b1f6d3601d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "1381104faaa5c51b2cc540f185e72d3bbab0ea3eb57348c50af4440c7e469491"}, + "igniter": {:hex, :igniter, "0.3.14", "9b93dce6b40a51306ce6f11e5951d81df7eb7cd4e119ba8a781ca3eeca8dd98e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "d4cf3ca7cdad5e10cb9767e5947d4e358557ed310f1333db26892e96bf3950ff"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,7 +39,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.5.0", "3e65d5fbb1a8e2864ad6411262c8018fee73474f5789dda12285c82999253d5d", [:mix], [], "hexpm", "4a32b5d189d8453f73278c15712f8731b89e9211e50726b798214b303b51bfc7"}, - "spark": {:hex, :spark, "2.2.10", "834c5f6c6874d019116096b82fe8a3e9bfe92077c3e06ead14b6daff528b69ef", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "78972edb0cc1539e56d42f08aabc88b8b2892d64e3e8bd44d58113b7f63622fa"}, + "spark": {:hex, :spark, "2.2.11", "6589ac0e50d69e5095871a5e8f3bb6107755b1cc71f05a31d7398902506dab9a", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "662d297d0ad49a5990a72cbf342d70e90894218062da2893f2df529f70ecc2b4"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/filter_test.exs b/test/filter_test.exs index a1abe491..cff2dae4 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -785,6 +785,37 @@ defmodule AshPostgres.FilterTest do ) |> Ash.read!() end + + test "it works with synthesized to-one relationships" do + for i <- 1..4 do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: to_string(i), category: "foo"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "comment"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + if rem(i, 2) == 0 do + Comment + |> Ash.Changeset.for_create(:create, %{title: "later_comment"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + end + + post + end + + matching_post_count = + Post + |> Ash.Query.filter(exists(latest_comment, title == "later_comment")) + |> Ash.read!() + |> Enum.count() + + assert 2 = matching_post_count + end end describe "filtering on enum types" do From 2ddc53885b9074714bb2af8800c6e69d75368502 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 31 Jul 2024 17:48:55 -0400 Subject: [PATCH 0627/1215] improvement: dynamically select and allow setting a repo --- lib/data_layer.ex | 18 ++++-- lib/igniter.ex | 84 +++++++++++++++++++++++++++ lib/mix/tasks/ash_postgres.install.ex | 12 +--- 3 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 lib/igniter.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 798b4a47..6f85e935 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2685,9 +2685,10 @@ defmodule AshPostgres.DataLayer do @impl true def update(resource, changeset) do source = resolve_source(resource, changeset) + modifying = - Map.keys(changeset.attributes) ++ - Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) + Map.keys(changeset.attributes) ++ + Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) query = from(row in source, as: ^0) @@ -2945,7 +2946,7 @@ defmodule AshPostgres.DataLayer do end end - def install(igniter, module, Ash.Resource, _path, _argv) do + def install(igniter, module, Ash.Resource, _path, argv) do table_name = module |> Module.split() @@ -2953,7 +2954,16 @@ defmodule AshPostgres.DataLayer do |> Macro.underscore() |> Inflex.pluralize() - repo = Igniter.Code.Module.module_name("Repo") + {options, _, _} = OptionParser.parse(argv, switches: [repo: :string]) + + repo = + case options[:repo] do + nil -> + Igniter.Code.Module.module_name("Repo") + + repo -> + Igniter.Code.Module.parse(repo) + end igniter |> Spark.Igniter.set_option(module, [:postgres, :table], table_name) diff --git a/lib/igniter.ex b/lib/igniter.ex new file mode 100644 index 00000000..cc99b05c --- /dev/null +++ b/lib/igniter.ex @@ -0,0 +1,84 @@ +defmodule AshPostgres.Igniter do + @moduledoc "Codemods and utilities for working with AshPostgres & Igniter" + + @doc false + def default_repo_contents(otp_app) do + """ + use AshPostgres.Repo, otp_app: #{inspect(otp_app)} + + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """ + end + + def add_postgres_extension(igniter, repo_name, extension) do + Igniter.Code.Module.find_and_update_module!(igniter, repo_name, fn zipper -> + case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do + {:ok, zipper} -> + case Igniter.Code.List.append_new_to_list(zipper, extension) do + {:ok, zipper} -> + {:ok, zipper} + + _ -> + {:warning, + "Could not add installed extension #{inspect(extension)} to #{inspect(repo_name)}.installed_extensions/0"} + end + + _ -> + zipper = Sourceror.Zipper.rightmost(zipper) + + code = """ + def installed_extensions do + [#{inspect(extension)}] + end + """ + + {:ok, Igniter.Code.Common.add_code(zipper, code)} + end + end) + end + + def select_repo(igniter, opts \\ []) do + label = Keyword.get(opts, :label, "Which repo should be used?") + generate = Keyword.get(opts, :generate?, false) + + case list_repos(igniter) do + {igniter, []} -> + if generate do + repo = Igniter.Code.Module.module_name("Repo") + otp_app = Igniter.Project.Application.app_name() + + igniter = + Igniter.Code.Module.create_module(igniter, repo, default_repo_contents(otp_app)) + + {igniter, repo} + else + {igniter, nil} + end + + {igniter, [repo]} -> + {igniter, repo} + + {igniter, repos} -> + {igniter, Owl.IO.select(repos, label: label, render_as: &inspect/1)} + end + end + + def list_repos(igniter) do + Igniter.Code.Module.find_all_matching_modules(igniter, fn _mod, zipper -> + move_to_repo_use(zipper) != :error + end) + end + + defp move_to_repo_use(zipper) do + Igniter.Code.Function.move_to_function_call(zipper, :use, 2, fn zipper -> + Igniter.Code.Function.argument_equals?( + zipper, + 0, + AshPostgres.Repo + ) + end) + end +end diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 48d490c9..26127915 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -250,20 +250,10 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp setup_repo_module(igniter, otp_app, repo) do - default_repo_contents = - """ - use AshPostgres.Repo, otp_app: #{inspect(otp_app)} - - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] - end - """ - Igniter.Code.Module.find_and_update_or_create_module( igniter, repo, - default_repo_contents, + AshPostgres.Igniter.default_repo_contents(otp_app), fn zipper -> zipper |> set_otp_app(otp_app) From bc0cb6babdd434dac3253d146dadd99f6626cb7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:23:46 -0400 Subject: [PATCH 0628/1215] chore(deps): bump the production-dependencies group with 2 updates (#365) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.3.1 to 3.3.2 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.3.1...v3.3.2) Updates `igniter` from 0.3.14 to 0.3.16 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.14...v0.3.16) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 3108ce27..50de2aee 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.3.1", "fc67719590b3f3488f90b267666364f6ac364e7658bee3806c2739c9850d05d9", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de8568f528194edd6d22f8941f5f67589788fe9a3868e900efac81e2ded25955"}, + "ash": {:hex, :ash, "3.3.2", "e5bb78f4fde87e3b445675e0d1691c81aecc3f5835bac625b6cc72ab8da13061", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd264da49cf737318506f8b6b3c3b1aeb925e5ad6ec8ca27786b341ef9566446"}, "ash_sql": {:hex, :ash_sql, "0.2.29", "d99a40818667d1843e61edae6b3eeeaedda62f34e6477e4dd29017f81fddbf07", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1d3cc1c3a26cf87bb913fdf7bcbe9d901d35f260aac36225b8054541836e5752"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.14", "9b93dce6b40a51306ce6f11e5951d81df7eb7cd4e119ba8a781ca3eeca8dd98e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "d4cf3ca7cdad5e10cb9767e5947d4e358557ed310f1333db26892e96bf3950ff"}, + "igniter": {:hex, :igniter, "0.3.16", "5967e06e26379cb7531aa7c4fa9ab8a4899f75d12f00d5453565ca748c24e6ae", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "ccbce6aab2e0f1d1c34f7a19e025e8f66ed970b7ec6e35cc823164625afbf843"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 2d1c7d48ceb85d1d2d411f8e2e03e2bfa4f0ca5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Wed, 7 Aug 2024 16:38:00 +0200 Subject: [PATCH 0629/1215] chore: Cleanup Test Output (#366) --- .../migration_generator.ex | 4 +- test/migration_generator_test.exs | 81 +++++++++---------- test/mix_squash_snapshots_test.exs | 14 +++- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index a6138ebc..ff8f1d41 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -379,7 +379,7 @@ defmodule AshPostgres.MigrationGenerator do operations -> if opts.check do - IO.puts(""" + Mix.shell().error(""" Migrations would have been generated, but the --check flag was provided. To see what migration would have been generated, run with the `--dry-run` @@ -1028,7 +1028,7 @@ defmodule AshPostgres.MigrationGenerator do end rescue exception -> - IO.puts(""" + Mix.shell().error(""" Exception while formatting: #{inspect(exception)} diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index de8e12ae..a5648c43 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -4,7 +4,17 @@ defmodule AshPostgres.MigrationGeneratorTest do import ExUnit.CaptureLog - defmacrop defposts(mod \\ Post, do: body) do + setup do + current_shell = Mix.shell() + + :ok = Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(current_shell) + end) + end + + defmacrop defresource(mod, do: body) do quote do Code.compiler_options(ignore_module_conflict: true) @@ -13,6 +23,16 @@ defmodule AshPostgres.MigrationGeneratorTest do domain: nil, data_layer: AshPostgres.DataLayer + unquote(body) + end + + Code.compiler_options(ignore_module_conflict: false) + end + end + + defmacrop defposts(mod \\ Post, do: body) do + quote do + defresource unquote(mod) do postgres do table "posts" repo(AshPostgres.TestRepo) @@ -30,8 +50,23 @@ defmodule AshPostgres.MigrationGeneratorTest do unquote(body) end + end + end - Code.compiler_options(ignore_module_conflict: false) + defmacrop defcomments(mod \\ Comment, do: body) do + quote do + defresource unquote(mod) do + postgres do + table "comments" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + unquote(body) + end end end @@ -108,8 +143,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post]) - Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", @@ -208,8 +241,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post]) - Mix.shell(Mix.Shell.Process) - {:ok, _} = Ecto.Adapters.SQL.query( AshPostgres.TestRepo, @@ -291,7 +322,6 @@ defmodule AshPostgres.MigrationGeneratorTest do end defdomain([Post]) - Mix.shell(Mix.Shell.Process) AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", @@ -336,7 +366,6 @@ defmodule AshPostgres.MigrationGeneratorTest do end defdomain([Post]) - Mix.shell(Mix.Shell.Process) AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", @@ -378,8 +407,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post]) - Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", @@ -470,8 +497,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post]) - Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", @@ -833,8 +858,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post]) - Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", @@ -1635,11 +1658,7 @@ defmodule AshPostgres.MigrationGeneratorTest do File.rm_rf!("test_migration_path") end) - defmodule Comment do - use Ash.Resource, - domain: nil, - data_layer: AshPostgres.DataLayer - + defcomments do postgres do polymorphic?(true) repo(AshPostgres.TestRepo) @@ -1832,16 +1851,7 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defmodule Comment do - use Ash.Resource, - domain: nil, - data_layer: AshPostgres.DataLayer - - postgres do - table "comments" - repo AshPostgres.TestRepo - end - + defcomments do attributes do uuid_primary_key(:id) end @@ -1855,8 +1865,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post, Comment]) - Mix.shell(Mix.Shell.Process) - AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", @@ -1881,16 +1889,7 @@ defmodule AshPostgres.MigrationGeneratorTest do end end - defmodule Comment do - use Ash.Resource, - domain: nil, - data_layer: AshPostgres.DataLayer - - postgres do - table "comments" - repo AshPostgres.TestRepo - end - + defcomments do attributes do uuid_primary_key(:id) end diff --git a/test/mix_squash_snapshots_test.exs b/test/mix_squash_snapshots_test.exs index 5c934b27..f2827228 100644 --- a/test/mix_squash_snapshots_test.exs +++ b/test/mix_squash_snapshots_test.exs @@ -2,6 +2,16 @@ defmodule AshPostgres.MixSquashSnapshotsTest do use AshPostgres.RepoCase, async: false @moduletag :migration + setup do + current_shell = Mix.shell() + + :ok = Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(current_shell) + end) + end + defmacrop defposts(mod \\ Post, do: body) do quote do Code.compiler_options(ignore_module_conflict: true) @@ -67,8 +77,6 @@ defmodule AshPostgres.MixSquashSnapshotsTest do File.rm_rf!("test_migration_path") end) - Mix.shell(Mix.Shell.Process) - defposts do identities do identity(:title, [:title]) @@ -154,8 +162,6 @@ defmodule AshPostgres.MixSquashSnapshotsTest do File.rm_rf!("test_migration_path") end) - Mix.shell(Mix.Shell.Process) - defposts do identities do identity(:title, [:title]) From cdd94a8b930531f26369f5fc24dab8a41b4e94b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 08:53:03 -0400 Subject: [PATCH 0630/1215] chore(deps): bump the production-dependencies group with 3 updates (#367) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [igniter](https://github.com/ash-project/igniter) and [postgrex](https://github.com/elixir-ecto/postgrex). Updates `ash` from 3.3.2 to 3.3.3 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.3.2...v3.3.3) Updates `igniter` from 0.3.16 to 0.3.17 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.16...v0.3.17) Updates `postgrex` from 0.18.0 to 0.19.0 - [Release notes](https://github.com/elixir-ecto/postgrex/releases) - [Changelog](https://github.com/elixir-ecto/postgrex/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/postgrex/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: postgrex dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 50de2aee..07af5576 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.3.2", "e5bb78f4fde87e3b445675e0d1691c81aecc3f5835bac625b6cc72ab8da13061", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd264da49cf737318506f8b6b3c3b1aeb925e5ad6ec8ca27786b341ef9566446"}, + "ash": {:hex, :ash, "3.3.3", "1e4047c867e064fe5edabd86c7366dbf3e094a91a2f5269ee0169eb310931e7c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0486ec2185deeca68d617eb016d22e598af0e44e7512453f959124f4345ae9df"}, "ash_sql": {:hex, :ash_sql, "0.2.29", "d99a40818667d1843e61edae6b3eeeaedda62f34e6477e4dd29017f81fddbf07", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1d3cc1c3a26cf87bb913fdf7bcbe9d901d35f260aac36225b8054541836e5752"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.16", "5967e06e26379cb7531aa7c4fa9ab8a4899f75d12f00d5453565ca748c24e6ae", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "ccbce6aab2e0f1d1c34f7a19e025e8f66ed970b7ec6e35cc823164625afbf843"}, + "igniter": {:hex, :igniter, "0.3.17", "177d9c5d4895908b84021d274e6f12c1dd9ac81401a011157efe92a7c63a8196", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "f851584a9403a151a82809d5b1db7d5dda4b35dec5aff7829e75616917d9c9e2"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -33,7 +33,7 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, - "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, + "postgrex": {:hex, :postgrex, "0.19.0", "f7d50e50cb42e0a185f5b9a6095125a9ab7e4abccfbe2ab820ab9aa92b71dbab", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "dba2d2a0a8637defbf2307e8629cb2526388ba7348f67d04ec77a5d6a72ecfae"}, "reactor": {:hex, :reactor, "0.9.0", "f48af9f300454b979a22d5a04b18b59e16959478ffa7f88d50b5e142b5d055dc", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c5ffd700ac669d0992a9e296978abe2110670b23addc0970fca9108d506489c"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, From cc7a7615125547786abb8badb497ee04aa6d12f7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 8 Aug 2024 10:01:33 -0400 Subject: [PATCH 0631/1215] chore: make test assertion clearer to debug CI --- test/aggregate_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 9c8c27d8..02d2add3 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -616,11 +616,12 @@ defmodule AshSql.AggregateTest do |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.create!() - assert %{first_comment_nils_first_include_nil: "match"} = + assert "match" == Post |> Ash.Query.filter(id == ^post.id) |> Ash.Query.load(:first_comment_nils_first_include_nil) |> Ash.read_one!() + |> Map.get(:first_comment_nils_first_include_nil) end test "it can be sorted on" do From 769f8bfccfda34d74b31ff7748e3915b3f71825e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 9 Aug 2024 16:09:49 -0400 Subject: [PATCH 0632/1215] fix: update ash_sql for exists aggregate fixes --- mix.exs | 2 +- mix.lock | 2 +- test/aggregate_test.exs | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 917cc54b..42de7696 100644 --- a/mix.exs +++ b/mix.exs @@ -163,7 +163,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.3")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.29")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.11 and >= 3.11.3"}, {:ecto, "~> 3.11 and >= 3.11.2"}, diff --git a/mix.lock b/mix.lock index 07af5576..cc085f2d 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.17", "177d9c5d4895908b84021d274e6f12c1dd9ac81401a011157efe92a7c63a8196", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "f851584a9403a151a82809d5b1db7d5dda4b35dec5aff7829e75616917d9c9e2"}, + "igniter": {:hex, :igniter, "0.3.18", "da7a08eba965a89282c3a8642d7ccc718be65b09aef3d77312dbb27e3f288466", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "91c7b011cfa6b5036cd84757a3a8a8e009ef59c06956789f598ada3eb8be2fc4"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 02d2add3..62f2d81e 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -334,6 +334,24 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + test "exists aggregates can be referenced in nested filters" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Logger.configure(level: :debug) + + assert Comment + |> Ash.Query.filter(post.has_comment_called_match) + |> Ash.read_one!() + end + test "exists aggregates can be used at the query level" do post = Post From 4db75671ed5f188ce084a77c6c292a50ffea61ac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 9 Aug 2024 16:10:21 -0400 Subject: [PATCH 0633/1215] chore: release version v2.1.18 --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a67b47..ee30a3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,65 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.18](https://github.com/ash-project/ash_postgres/compare/v2.1.17...v2.1.18) (2024-08-09) + + + + +### Bug Fixes: + +* update ash_sql for exists aggregate fixes + +* don't overwrite non-updated fields on update + +* ensure app is compiled before using repo modules + +* use a subquery if any exists aggregates are in play + +* properly convert tenant to string when building lateral join + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* dynamically select and allow setting a repo + +* update ash_sql for latest fixes + +* update ash & ash_sql for various fixes + +* update ash_sql for cleaner queries + +* update ash_sql dependencies for bug fixes + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.17](https://github.com/ash-project/ash_postgres/compare/v2.1.16...v2.1.17) (2024-07-27) diff --git a/mix.exs b/mix.exs index 42de7696..7137fd11 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.17" + @version "2.1.18" def project do [ From ed602671735db23788f05836e64004c9c55e3254 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 9 Aug 2024 16:10:39 -0400 Subject: [PATCH 0634/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index cc085f2d..b9cf57bf 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.3.3", "1e4047c867e064fe5edabd86c7366dbf3e094a91a2f5269ee0169eb310931e7c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0486ec2185deeca68d617eb016d22e598af0e44e7512453f959124f4345ae9df"}, - "ash_sql": {:hex, :ash_sql, "0.2.29", "d99a40818667d1843e61edae6b3eeeaedda62f34e6477e4dd29017f81fddbf07", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1d3cc1c3a26cf87bb913fdf7bcbe9d901d35f260aac36225b8054541836e5752"}, + "ash_sql": {:hex, :ash_sql, "0.2.30", "244a2071c7fdaec486a186136e52a635dc01f2c1db774d3c506562b339efcf1c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e93f704b5b29caf0793dd880ec10b1fd313cc0e0a385074955ca36d8af530e4e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, From 5e8ac4bf7e63a8151f96c36f3deb88e9587cf851 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Aug 2024 14:22:21 -0400 Subject: [PATCH 0635/1215] fix: we missed a change when preparing for ecto 3.12 parameterized type changes --- lib/sql_implementation.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 7c556076..6a26291a 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -249,11 +249,13 @@ defmodule AshPostgres.SqlImplementation do new_returns = case new_returns do + {:parameterized, _} = parameterized -> parameterized {type, constraints} -> parameterized_type(type, constraints) other -> other end {Enum.map(types, fn + {:parameterized, _} = parameterized -> parameterized {type, constraints} -> parameterized_type(type, constraints) From 8f3005294473db3a854d5c13ac8faff9f73d609c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Aug 2024 14:25:04 -0400 Subject: [PATCH 0636/1215] chore: update mix.lock --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index b9cf57bf..3bdcffe5 100644 --- a/mix.lock +++ b/mix.lock @@ -10,8 +10,8 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, + "ecto": {:hex, :ecto, "3.12.0", "9014a3ccac7f91e680b9d237d461ebe3d4e16d62ca8e355d540e2c6afdc28309", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "41e781a76e131093af8e1edf68b1319bf320878faff58da41ffa4b10fc6ff678"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -34,11 +34,11 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, "postgrex": {:hex, :postgrex, "0.19.0", "f7d50e50cb42e0a185f5b9a6095125a9ab7e4abccfbe2ab820ab9aa92b71dbab", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "dba2d2a0a8637defbf2307e8629cb2526388ba7348f67d04ec77a5d6a72ecfae"}, - "reactor": {:hex, :reactor, "0.9.0", "f48af9f300454b979a22d5a04b18b59e16959478ffa7f88d50b5e142b5d055dc", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c5ffd700ac669d0992a9e296978abe2110670b23addc0970fca9108d506489c"}, + "reactor": {:hex, :reactor, "0.9.1", "082f8e9b1fd7586c0a016c2fb533835fec7eaef5ffb0263abb4473106c20b1ca", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7191ddf95fdd2b65770a57a2e38dd502a94909e51ac8daf497330e67fc032dc3"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.5.0", "3e65d5fbb1a8e2864ad6411262c8018fee73474f5789dda12285c82999253d5d", [:mix], [], "hexpm", "4a32b5d189d8453f73278c15712f8731b89e9211e50726b798214b303b51bfc7"}, + "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, "spark": {:hex, :spark, "2.2.11", "6589ac0e50d69e5095871a5e8f3bb6107755b1cc71f05a31d7398902506dab9a", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "662d297d0ad49a5990a72cbf342d70e90894218062da2893f2df529f70ecc2b4"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, From e8027f6e9d7e41ecf341e589f6bf5dd2a5cc025f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Aug 2024 14:25:42 -0400 Subject: [PATCH 0637/1215] chore: release version v2.1.19 --- CHANGELOG.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee30a3f4..0551a506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,67 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.1.19](https://github.com/ash-project/ash_postgres/compare/v2.1.18...v2.1.19) (2024-08-12) + + + + +### Bug Fixes: + +* we missed a change when preparing for ecto 3.12 parameterized type changes + +* update ash_sql for exists aggregate fixes + +* don't overwrite non-updated fields on update + +* ensure app is compiled before using repo modules + +* use a subquery if any exists aggregates are in play + +* properly convert tenant to string when building lateral join + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* dynamically select and allow setting a repo + +* update ash_sql for latest fixes + +* update ash & ash_sql for various fixes + +* update ash_sql for cleaner queries + +* update ash_sql dependencies for bug fixes + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.18](https://github.com/ash-project/ash_postgres/compare/v2.1.17...v2.1.18) (2024-08-09) diff --git a/mix.exs b/mix.exs index 7137fd11..77c1e3c9 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.18" + @version "2.1.19" def project do [ From 73c7a915e00b06e4119935bbedf674106687a668 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 12 Aug 2024 14:45:04 -0400 Subject: [PATCH 0638/1215] chore: format --- lib/sql_implementation.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 6a26291a..71ec3cf2 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -255,7 +255,9 @@ defmodule AshPostgres.SqlImplementation do end {Enum.map(types, fn - {:parameterized, _} = parameterized -> parameterized + {:parameterized, _} = parameterized -> + parameterized + {type, constraints} -> parameterized_type(type, constraints) From a539f6443ef52a601349c70cafbf0d91d42595d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Mon, 12 Aug 2024 20:48:45 +0200 Subject: [PATCH 0639/1215] fix: handle filter condition on create (#368) --- lib/data_layer.ex | 22 +++++++++++++ test/bulk_create_test.exs | 44 ++++++++++++++++++++++++++ test/create_test.exs | 58 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 12 ++++++- test/test_helper.exs | 2 +- 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 test/create_test.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6f85e935..c56fbf9f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2539,6 +2539,28 @@ defmodule AshPostgres.DataLayer do upsert_fields: upsert_fields, return_records?: true }) do + {:ok, []} -> + key_filters = + Enum.map(keys, fn key -> + {key, + Ash.Changeset.get_attribute(changeset, key) || Map.get(changeset.params, key) || + Map.get(changeset.params, to_string(key))} + end) + + ash_query = + resource + |> Ash.Query.do_filter(and: [key_filters]) + |> then(fn + query when is_nil(identity) or is_nil(identity.where) -> query + query -> Ash.Query.do_filter(query, identity.where) + end) + |> Ash.Query.set_tenant(changeset.tenant) + + with {:ok, ecto_query} <- Ash.Query.data_layer_query(ash_query), + {:ok, [result]} <- run_query(ecto_query, resource) do + {:ok, Ash.Resource.put_metadata(result, :upsert_skipped, true)} + end + {:ok, [result]} -> {:ok, result} diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index f4edee0b..bdfdd497 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -65,6 +65,50 @@ defmodule AshPostgres.BulkCreateTest do end) end + test "bulk upsert skips with filter" do + assert [ + {:ok, %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}}, + {:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}}, + {:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}} + ] = + Ash.bulk_create!( + [ + %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}, + %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}, + %{title: "herbert", uniq_if_contains_foo: "3", price: 30} + ], + Post, + :create, + return_stream?: true, + return_records?: true + ) + |> Enum.sort_by(fn {:ok, result} -> result.title end) + + assert [ + {:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000}}, + {:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}} + ] = + Ash.bulk_create!( + [ + %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}, + %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000}, + %{title: "herbert", uniq_if_contains_foo: "3", price: 30} + ], + Post, + :upsert_with_filter, + return_stream?: true, + return_errors?: true, + return_records?: true + ) + |> Enum.sort_by(fn + {:ok, result} -> + result.title + + _ -> + nil + end) + end + # confirmed that this doesn't work because it can't. An upsert must map to a potentially successful insert. # leaving this test here for posterity # test "bulk creates can upsert with id" do diff --git a/test/create_test.exs b/test/create_test.exs new file mode 100644 index 00000000..0f461dfc --- /dev/null +++ b/test/create_test.exs @@ -0,0 +1,58 @@ +defmodule AshPostgres.CreateTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Post + + test "creates insert" do + assert {:ok, %Post{}} = + Post + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create() + + assert [%{title: "fred"}] = + Post + |> Ash.Query.sort(:title) + |> Ash.read!() + end + + test "upserts entry" do + assert {:ok, %Post{id: id}} = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "fredfoo", + uniq_if_contains_foo: "foo", + price: 10 + }) + |> Ash.create() + + assert {:ok, %Post{id: ^id, price: 20}} = + Post + |> Ash.Changeset.for_create(:upsert_with_filter, %{ + title: "fredfoo", + uniq_if_contains_foo: "foo", + price: 20 + }) + |> Ash.create() + end + + test "skips upsert with filter" do + assert {:ok, %Post{id: id}} = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "fredfoo", + uniq_if_contains_foo: "foo", + price: 10 + }) + |> Ash.create() + + assert {:ok, %Post{id: ^id} = post} = + Post + |> Ash.Changeset.for_create(:upsert_with_filter, %{ + title: "fredfoo", + uniq_if_contains_foo: "foo", + price: 10 + }) + |> Ash.create() + + assert Ash.Resource.get_metadata(post, :upsert_skipped) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 473a0103..468afe0f 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -258,6 +258,16 @@ defmodule AshPostgres.Test.Post do ) end + create :upsert_with_filter do + upsert?(true) + upsert_identity(:uniq_if_contains_foo) + upsert_fields([:price]) + + change(fn changeset, _ -> + Ash.Changeset.filter(changeset, expr(price != fragment("EXCLUDED.price"))) + end) + end + update :set_title_from_author do change(atomic_update(:title, expr(author.first_name))) end @@ -292,7 +302,7 @@ defmodule AshPostgres.Test.Post do identity(:uniq_on_upper, [:upper_thing]) identity(:uniq_if_contains_foo, [:uniq_if_contains_foo]) do - where expr(contains(title, "foo")) + where expr(contains(uniq_if_contains_foo, "foo")) end end diff --git a/test/test_helper.exs b/test/test_helper.exs index fd6fc1e0..6a184284 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.start() +ExUnit.start(capture_log: true) ExUnit.configure(stacktrace_depth: 100) AshPostgres.TestRepo.start_link() From 10cf60cdc1c69ea6e8044cacd3fba9fc83eb265d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 13 Aug 2024 09:57:31 -0400 Subject: [PATCH 0640/1215] fix: remove `Agent` "convenience" for determining min pg version We need to require that users provide this function. To that end we're adding a warning in a minor release branch telling users to define this. The agent was acting as a bottleneck that all queries must go through, causing nontrivial performance issues at scale. --- lib/data_layer/info.ex | 11 +++- lib/repo.ex | 106 +------------------------------------ lib/repo/before_compile.ex | 25 +++++++++ mix.lock | 2 +- test/aggregate_test.exs | 2 - test/support/test_repo.ex | 4 ++ 6 files changed, 40 insertions(+), 110 deletions(-) create mode 100644 lib/repo/before_compile.ex diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 5dcec0ad..7f66ff2c 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -42,8 +42,15 @@ defmodule AshPostgres.DataLayer.Info do @doc "Gets the resource's repo's postgres version" def min_pg_version(resource) do case repo(resource, :read).min_pg_version() do - %Version{} = version -> version - string when is_binary(string) -> Version.parse!(string) + %Version{} = version -> + version + + string when is_binary(string) -> + IO.warn( + "Got a `string` for min_pg_version, expected a `Version` struct. Got: #{inspect(string)}. Please call `Version.parse!` before returning the value." + ) + + Version.parse!(string) end end diff --git a/lib/repo.ex b/lib/repo.ex index b6a49815..d46107bb 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -86,10 +86,10 @@ defmodule AshPostgres.Repo do otp_app: otp_app end - @agent __MODULE__.AshPgVersion @behaviour AshPostgres.Repo @warn_on_missing_ash_functions Keyword.get(opts, :warn_on_missing_ash_functions?, true) @after_compile __MODULE__ + @before_compile AshPostgres.Repo.BeforeCompile require Logger defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1 @@ -123,18 +123,6 @@ defmodule AshPostgres.Repo do end def init(type, config) do - if type == :supervisor do - try do - Agent.stop(@agent) - rescue - _ -> - :ok - catch - _, _ -> - :ok - end - end - new_config = config |> Keyword.put(:installed_extensions, installed_extensions()) @@ -208,97 +196,6 @@ defmodule AshPostgres.Repo do end end - def min_pg_version do - if version = cached_version() do - version - else - lookup_version() - end - end - - defp cached_version do - if config()[:pool] == Ecto.Adapters.SQL.Sandbox do - Agent.start_link( - fn -> - nil - end, - name: @agent - ) - - case Agent.get(@agent, fn state -> state end) do - nil -> - version = lookup_version() - - Agent.update(@agent, fn _ -> - version - end) - - version - - version -> - version - end - else - Agent.start_link( - fn -> - lookup_version() - end, - name: @agent - ) - - Agent.get(@agent, fn state -> state end) - end - end - - defp lookup_version do - version_string = - try do - query!("SELECT version()").rows |> Enum.at(0) |> Enum.at(0) - rescue - error -> - reraise """ - Got an error while trying to read postgres version - - Error: - - #{inspect(error)} - """, - __STACKTRACE__ - end - - try do - version_string - |> String.split(" ") - |> Enum.at(1) - |> String.split(".") - |> case do - [major] -> - "#{major}.0.0" - - [major, minor] -> - "#{major}.#{minor}.0" - - other -> - Enum.join(other, ".") - end - |> Version.parse!() - rescue - error -> - reraise( - """ - Could not parse postgres version from version string: "#{version_string}" - - You may need to define the `min_version/0` callback yourself. - - Error: - - #{inspect(error)} - """, - __STACKTRACE__ - ) - end - end - def from_ecto(other), do: other def to_ecto(nil), do: nil @@ -336,7 +233,6 @@ defmodule AshPostgres.Repo do defoverridable init: 2, on_transaction_begin: 1, installed_extensions: 0, - min_pg_version: 0, all_tenants: 0, tenant_migrations_path: 0, default_prefix: 0, diff --git a/lib/repo/before_compile.ex b/lib/repo/before_compile.ex new file mode 100644 index 00000000..a53f96d0 --- /dev/null +++ b/lib/repo/before_compile.ex @@ -0,0 +1,25 @@ +defmodule AshPostgres.Repo.BeforeCompile do + @moduledoc false + + defmacro __before_compile__(_env) do + quote do + unless Module.defines?(__MODULE__, {:min_pg_version, 0}, :def) do + IO.warn(""" + Please define `min_pg_version/0` in repo module: #{inspect(__MODULE__)} + + For example: + + def min_pg_version do + %Version{major: 16, minor: 0, patch: 0} + end + + The lowest compatible version is being assumed. + """) + + def min_pg_version do + %Version{major: 13, minor: 0, patch: 0} + end + end + end + end +end diff --git a/mix.lock b/mix.lock index 3bdcffe5..7d50c371 100644 --- a/mix.lock +++ b/mix.lock @@ -39,7 +39,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.11", "6589ac0e50d69e5095871a5e8f3bb6107755b1cc71f05a31d7398902506dab9a", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "662d297d0ad49a5990a72cbf342d70e90894218062da2893f2df529f70ecc2b4"}, + "spark": {:hex, :spark, "2.2.16", "221f18b302f8b2df28ac5664c4a1abc8b9c1ba493676d9dc77234a8dbfcd07ae", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "143d3f78d717556041a1b89f523ba7f3004f90351a567e9ea9044f90afcd1085"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 62f2d81e..630aeaac 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -345,8 +345,6 @@ defmodule AshSql.AggregateTest do |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.create!() - Logger.configure(level: :debug) - assert Comment |> Ash.Query.filter(post.has_comment_called_match) |> Ash.read_one!() diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 50d1b472..094b947a 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -12,6 +12,10 @@ defmodule AshPostgres.TestRepo do Application.get_env(:ash_postgres, :no_extensions, []) end + def min_pg_version do + %Version{major: 16, minor: 0, patch: 0} + end + def all_tenants do Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) From 8244a203e0b60b0ce65c087259b8a356319715cf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 13 Aug 2024 09:58:42 -0400 Subject: [PATCH 0641/1215] chore: release version v2.2.0 --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0551a506..a006b836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,71 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.2.0](https://github.com/ash-project/ash_postgres/compare/v2.1.19...v2.2.0) (2024-08-13) + + + + +### Bug Fixes: + +* remove `Agent` "convenience" for determining min pg version + +* handle filter condition on create (#368) + +* we missed a change when preparing for ecto 3.12 parameterized type changes + +* update ash_sql for exists aggregate fixes + +* don't overwrite non-updated fields on update + +* ensure app is compiled before using repo modules + +* use a subquery if any exists aggregates are in play + +* properly convert tenant to string when building lateral join + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* dynamically select and allow setting a repo + +* update ash_sql for latest fixes + +* update ash & ash_sql for various fixes + +* update ash_sql for cleaner queries + +* update ash_sql dependencies for bug fixes + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.1.19](https://github.com/ash-project/ash_postgres/compare/v2.1.18...v2.1.19) (2024-08-12) diff --git a/mix.exs b/mix.exs index 77c1e3c9..856bedd3 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.1.19" + @version "2.2.0" def project do [ From 9ac8c02eccb2d2e232a6979c52595fe3d064d42e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 13 Aug 2024 10:03:37 -0400 Subject: [PATCH 0642/1215] chore: fix changelog --- CHANGELOG.md | 225 +++------------------------------------------------ 1 file changed, 10 insertions(+), 215 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a006b836..e3aca9cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,241 +7,36 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.2.0](https://github.com/ash-project/ash_postgres/compare/v2.1.19...v2.2.0) (2024-08-13) - - - ### Bug Fixes: -* remove `Agent` "convenience" for determining min pg version - -* handle filter condition on create (#368) - -* we missed a change when preparing for ecto 3.12 parameterized type changes - -* update ash_sql for exists aggregate fixes - -* don't overwrite non-updated fields on update - -* ensure app is compiled before using repo modules - -* use a subquery if any exists aggregates are in play - -* properly convert tenant to string when building lateral join - -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - -### Improvements: - -* dynamically select and allow setting a repo - -* update ash_sql for latest fixes - -* update ash & ash_sql for various fixes - -* update ash_sql for cleaner queries - -* update ash_sql dependencies for bug fixes - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies +- [`AshPostgres.Repo`] remove `Agent` "convenience" for determining min pg version -* add `binding()` expression +We need to require that users provide this function. To that end we're +adding a warning in a minor release branch telling users to define this. +The agent was acting as a bottleneck that all queries must go through, +causing nontrivial performance issues at scale. -* use latest type casting code from ash - -* support new type determination code +- [upserts] handle filter condition on create (#368) ## [v2.1.19](https://github.com/ash-project/ash_postgres/compare/v2.1.18...v2.1.19) (2024-08-12) - - - ### Bug Fixes: -* we missed a change when preparing for ecto 3.12 parameterized type changes - -* update ash_sql for exists aggregate fixes - -* don't overwrite non-updated fields on update - -* ensure app is compiled before using repo modules - -* use a subquery if any exists aggregates are in play - -* properly convert tenant to string when building lateral join - -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - -### Improvements: - -* dynamically select and allow setting a repo - -* update ash_sql for latest fixes - -* update ash & ash_sql for various fixes - -* update ash_sql for cleaner queries - -* update ash_sql dependencies for bug fixes - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies +- [ecto compatibility] we missed a change when preparing for ecto 3.12 parameterized type changes -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code +- [exists aggregates] update ash_sql for exists aggregate fixes ## [v2.1.18](https://github.com/ash-project/ash_postgres/compare/v2.1.17...v2.1.18) (2024-08-09) - - - -### Bug Fixes: - -* update ash_sql for exists aggregate fixes - -* don't overwrite non-updated fields on update - -* ensure app is compiled before using repo modules - -* use a subquery if any exists aggregates are in play - -* properly convert tenant to string when building lateral join - -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - ### Improvements: -* dynamically select and allow setting a repo - -* update ash_sql for latest fixes - -* update ash & ash_sql for various fixes - -* update ash_sql for cleaner queries - -* update ash_sql dependencies for bug fixes - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code +- [`ash_postgres.gen.migration`] dynamically select and allow setting a repo ## [v2.1.17](https://github.com/ash-project/ash_postgres/compare/v2.1.16...v2.1.17) (2024-07-27) - - - -### Bug Fixes: - -* don't overwrite non-updated fields on update - -* ensure app is compiled before using repo modules - -* use a subquery if any exists aggregates are in play - -* properly convert tenant to string when building lateral join - -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set - ### Improvements: -* update ash & ash_sql for various fixes - -* update ash_sql for cleaner queries - -* update ash_sql dependencies for bug fixes - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code +- [`ash_sql`] update ash & ash_sql for various fixes ## [v2.1.16](https://github.com/ash-project/ash_postgres/compare/v2.1.15...v2.1.16) (2024-07-25) From 4f7b3cdcd02a85e5c76d702d8073f08af90e9abc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 06:23:08 -0400 Subject: [PATCH 0643/1215] chore(deps): bump the production-dependencies group with 4 updates (#371) --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 7d50c371..fa1f597c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.3.3", "1e4047c867e064fe5edabd86c7366dbf3e094a91a2f5269ee0169eb310931e7c", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0486ec2185deeca68d617eb016d22e598af0e44e7512453f959124f4345ae9df"}, + "ash": {:hex, :ash, "3.4.1", "14bfccd4c1e7c17db5aed1ecb5062875f55b56b67f6fba911f3a8ef6739f3cfd", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1e3127e0af0698e652a6bbfb4d4f1a3bb8a48fb42833f4e8f00eda8f1a93082b"}, "ash_sql": {:hex, :ash_sql, "0.2.30", "244a2071c7fdaec486a186136e52a635dc01f2c1db774d3c506562b339efcf1c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e93f704b5b29caf0793dd880ec10b1fd313cc0e0a385074955ca36d8af530e4e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -10,7 +10,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.12.0", "9014a3ccac7f91e680b9d237d461ebe3d4e16d62ca8e355d540e2c6afdc28309", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "41e781a76e131093af8e1edf68b1319bf320878faff58da41ffa4b10fc6ff678"}, + "ecto": {:hex, :ecto, "3.12.1", "626765f7066589de6fa09e0876a253ff60c3d00870dd3a1cd696e2ba67bfceea", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df0045ab9d87be947228e05a8d153f3e06e0d05ab10c3b3cc557d2f7243d1940"}, "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.18", "da7a08eba965a89282c3a8642d7ccc718be65b09aef3d77312dbb27e3f288466", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "91c7b011cfa6b5036cd84757a3a8a8e009ef59c06956789f598ada3eb8be2fc4"}, + "igniter": {:hex, :igniter, "0.3.19", "dfa4a05e94be72e9b75c9ce73b9ce408c9a5fe8f1f010eb36e711e70da698b46", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "f9b1ca6d11a7dcd1beb0bc0a1c08b8ce1fb4eb140a763037f3b79d9170cbf958"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -33,13 +33,13 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, - "postgrex": {:hex, :postgrex, "0.19.0", "f7d50e50cb42e0a185f5b9a6095125a9ab7e4abccfbe2ab820ab9aa92b71dbab", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "dba2d2a0a8637defbf2307e8629cb2526388ba7348f67d04ec77a5d6a72ecfae"}, + "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, "reactor": {:hex, :reactor, "0.9.1", "082f8e9b1fd7586c0a016c2fb533835fec7eaef5ffb0263abb4473106c20b1ca", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7191ddf95fdd2b65770a57a2e38dd502a94909e51ac8daf497330e67fc032dc3"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.16", "221f18b302f8b2df28ac5664c4a1abc8b9c1ba493676d9dc77234a8dbfcd07ae", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "143d3f78d717556041a1b89f523ba7f3004f90351a567e9ea9044f90afcd1085"}, + "spark": {:hex, :spark, "2.2.21", "b343f3488b5a986ad38d15ac124b9111b8f63f953e3a6ef3fad44fe129b7fad6", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "45b05c52a1afe4858e10b5e6b8cd33bff3cf0098afc144146c5ff05002d75a9d"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 97ef1437ccc472a8f15cb2a7ea5193df72c56ca1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Aug 2024 07:25:29 -0400 Subject: [PATCH 0644/1215] chore: CI/test/build fixes --- mix.exs | 4 ++-- test/calculation_test.exs | 28 ++++++++++++++++++++++++++++ test/support/resources/post.ex | 8 ++++++++ test/support/test_no_sandbox_repo.ex | 7 +++++++ test/support/test_repo.ex | 5 ++++- 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 856bedd3..2e7c5a7d 100644 --- a/mix.exs +++ b/mix.exs @@ -165,8 +165,8 @@ defmodule AshPostgres.MixProject do {:ash, ash_version("~> 3.3")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, {:igniter, "~> 0.3 and >= 0.3.6"}, - {:ecto_sql, "~> 3.11 and >= 3.11.3"}, - {:ecto, "~> 3.11 and >= 3.11.2"}, + {:ecto_sql, "~> 3.12"}, + {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies diff --git a/test/calculation_test.exs b/test/calculation_test.exs index e15dbbc8..38cc70f8 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -814,6 +814,34 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + # This test will pass on Ash 3.4.2+ + test "using calculations with input as anonymous aggregate fields works" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "abcdef"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "abc"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "abcd"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "abcde"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{max_comment_similarity: 0.625} = + Post + |> Ash.Query.load(max_comment_similarity: %{to: "abcdef"}) + |> Ash.read_one!() + end + test "exists with a relationship that has a filtered read action works" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 468afe0f..399254fa 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -551,6 +551,14 @@ defmodule AshPostgres.Test.Post do expr(count(comments, query: [filter: expr(title == "baz")])) ) + calculate( + :max_comment_similarity, + :float, + expr(max(comments, expr_type: :float, expr: fragment("similarity(?, ?)", title, ^arg(:to)))) + ) do + argument(:to, :string, allow_nil?: false) + end + calculate( :agg_map, :map, diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index f1222459..201d3667 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -7,6 +7,13 @@ defmodule AshPostgres.TestNoSandboxRepo do send(self(), data) end + def min_pg_version do + case System.get_env("PG_VERSION") do + nil -> %Version{major: 16, minor: 0, patch: 0} + version -> Version.parse!(version) + end + end + def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 094b947a..c12dad79 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -13,7 +13,10 @@ defmodule AshPostgres.TestRepo do end def min_pg_version do - %Version{major: 16, minor: 0, patch: 0} + case System.get_env("PG_VERSION") do + nil -> %Version{major: 16, minor: 0, patch: 0} + version -> Version.parse!(version) + end end def all_tenants do From e7ea05edc6abf425e6bc4a6676ff80c98ece6b4b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Aug 2024 07:48:29 -0400 Subject: [PATCH 0645/1215] chore: unlock deps --- mix.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/mix.lock b/mix.lock index fa1f597c..23427c91 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,6 @@ "ash_sql": {:hex, :ash_sql, "0.2.30", "244a2071c7fdaec486a186136e52a635dc01f2c1db774d3c506562b339efcf1c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e93f704b5b29caf0793dd880ec10b1fd313cc0e0a385074955ca36d8af530e4e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, @@ -45,7 +44,6 @@ "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, From d2b73fc3c3ef6d2fbf017abd2f3ffd6f8c0f8b61 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 15 Aug 2024 10:43:22 -0400 Subject: [PATCH 0646/1215] fix: set a proper default for `skip_unique_indexes` --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c56fbf9f..40bc5f52 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -331,7 +331,7 @@ defmodule AshPostgres.DataLayer do ], skip_unique_indexes: [ type: {:wrap_list, :atom}, - default: false, + default: [], doc: "Skip generating unique indexes when generating migrations" ], unique_index_names: [ From 8bf9864732f4937fc3ff493d8cf04fa343ea9b8c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 16 Aug 2024 15:39:53 -0400 Subject: [PATCH 0647/1215] improvement: include `min_pg_version` in new generators --- lib/igniter.ex | 5 +++++ lib/mix/tasks/ash_postgres.install.ex | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/igniter.ex b/lib/igniter.ex index cc99b05c..c7140277 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -6,6 +6,11 @@ defmodule AshPostgres.Igniter do """ use AshPostgres.Repo, otp_app: #{inspect(otp_app)} + def min_pg_version do + # Adjust this according to your postgres version + %Version{major: 16, minor: 0, patch: 0} + end + def installed_extensions do # Add extensions here, and the migration generator will install them. ["ash-functions"] diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 26127915..a310e905 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -341,4 +341,26 @@ defmodule Mix.Tasks.AshPostgres.Install do {:ok, zipper} end end + + defp configure_min_pg_version_function(zipper) do + case Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo) do + {:ok, zipper} -> + case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do + {:ok, zipper} -> + {:ok, zipper} + + _ -> + {:ok, + Igniter.Code.Common.add_code(zipper, """ + def min_pg_version do + # Adjust this according to your postgres version + %Version{major: 16, minor: 0, patch: 0} + end + """)} + end + + _ -> + {:ok, zipper} + end + end end From a94ee9823f35ea360657b8cb5837017c35cc9c0b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 16 Aug 2024 15:42:34 -0400 Subject: [PATCH 0648/1215] chore: release version v2.2.1 --- CHANGELOG.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3aca9cd..8df97c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,75 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.2.1](https://github.com/ash-project/ash_postgres/compare/v2.2.0...v2.2.1) (2024-08-16) + + + + +### Bug Fixes: + +* set a proper default for `skip_unique_indexes` + +* remove `Agent` "convenience" for determining min pg version + +* handle filter condition on create (#368) + +* we missed a change when preparing for ecto 3.12 parameterized type changes + +* update ash_sql for exists aggregate fixes + +* don't overwrite non-updated fields on update + +* ensure app is compiled before using repo modules + +* use a subquery if any exists aggregates are in play + +* properly convert tenant to string when building lateral join + +* update ash & ash_sql for fixes, test atomic alidations in destroys + +* properly add prod config in installer + +* properly perform or don't perform configuration modification code + +* allow non-unique has_many source_attributes (#355) + +* allow non-unique has_many source_attributes + +* update `ash_sql` for `parent_as` binding fix + +* update to latest ash version for aggregate fix + +* update ash_sql for include_nil? fix and test it + +* ensure synthesized query aggregates have context set + +### Improvements: + +* include `min_pg_version` in new generators + +* dynamically select and allow setting a repo + +* update ash_sql for latest fixes + +* update ash & ash_sql for various fixes + +* update ash_sql for cleaner queries + +* update ash_sql dependencies for bug fixes + +* prepend `:postgres` to section order + +* pluralize table name in extender + +* update ash/igniter dependencies + +* add `binding()` expression + +* use latest type casting code from ash + +* support new type determination code + ## [v2.2.0](https://github.com/ash-project/ash_postgres/compare/v2.1.19...v2.2.0) (2024-08-13) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 2e7c5a7d..4ac3c497 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.2.0" + @version "2.2.1" def project do [ From dda21bc6c4278d0c0e9a1b97740dc6f8e55c78cc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 16 Aug 2024 15:42:34 -0400 Subject: [PATCH 0649/1215] chore: release version v2.2.1 --- documentation/dsls/DSL:-AshPostgres.DataLayer.md | 2 +- lib/mix/tasks/ash_postgres.install.ex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL:-AshPostgres.DataLayer.md index bc5f18c8..305a925f 100644 --- a/documentation/dsls/DSL:-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL:-AshPostgres.DataLayer.md @@ -47,7 +47,7 @@ end | [`identity_wheres_to_sql`](#postgres-identity_wheres_to_sql){: #postgres-identity_wheres_to_sql } | `keyword` | | A keyword list of identity names and the SQL representation of their `where` clause. Used when creating unique indexes for identities over calculations | | [`base_filter_sql`](#postgres-base_filter_sql){: #postgres-base_filter_sql } | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | | [`simple_join_first_aggregates`](#postgres-simple_join_first_aggregates){: #postgres-simple_join_first_aggregates } | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | -| [`skip_unique_indexes`](#postgres-skip_unique_indexes){: #postgres-skip_unique_indexes } | `atom \| list(atom)` | `false` | Skip generating unique indexes when generating migrations | +| [`skip_unique_indexes`](#postgres-skip_unique_indexes){: #postgres-skip_unique_indexes } | `atom \| list(atom)` | `[]` | Skip generating unique indexes when generating migrations | | [`unique_index_names`](#postgres-unique_index_names){: #postgres-unique_index_names } | `list({list(atom), String.t} \| {list(atom), String.t, String.t})` | `[]` | A list of unique index names that could raise errors that are not configured in identities, or an mfa to a function that takes a changeset and returns the list. In the format `{[:affected, :keys], "name_of_constraint"}` or `{[:affected, :keys], "name_of_constraint", "custom error message"}` | | [`exclusion_constraint_names`](#postgres-exclusion_constraint_names){: #postgres-exclusion_constraint_names } | `any` | `[]` | A list of exclusion constraint names that could raise errors. Must be in the format `{:affected_key, "name_of_constraint"}` or `{:affected_key, "name_of_constraint", "custom error message"}` | | [`identity_index_names`](#postgres-identity_index_names){: #postgres-identity_index_names } | `any` | `[]` | A keyword list of identity names to the unique index name that they should use when being managed by the migration generator. | diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index a310e905..ceba0621 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -263,6 +263,7 @@ defmodule Mix.Tasks.AshPostgres.Install do |> remove_adapter_option() |> Sourceror.Zipper.top() |> configure_installed_extensions_function() + |> configure_min_pg_version_function() end ) end From 3fbd5514e09e7199cc7969b7a0dff36ab5fe60af Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 16 Aug 2024 15:45:38 -0400 Subject: [PATCH 0650/1215] chore: update changelog --- CHANGELOG.md | 63 ++-------------------------------------------------- 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8df97c1a..c8448f29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,72 +7,13 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.2.1](https://github.com/ash-project/ash_postgres/compare/v2.2.0...v2.2.1) (2024-08-16) - - - ### Bug Fixes: -* set a proper default for `skip_unique_indexes` - -* remove `Agent` "convenience" for determining min pg version - -* handle filter condition on create (#368) - -* we missed a change when preparing for ecto 3.12 parameterized type changes - -* update ash_sql for exists aggregate fixes - -* don't overwrite non-updated fields on update - -* ensure app is compiled before using repo modules - -* use a subquery if any exists aggregates are in play - -* properly convert tenant to string when building lateral join - -* update ash & ash_sql for fixes, test atomic alidations in destroys - -* properly add prod config in installer - -* properly perform or don't perform configuration modification code - -* allow non-unique has_many source_attributes (#355) - -* allow non-unique has_many source_attributes - -* update `ash_sql` for `parent_as` binding fix - -* update to latest ash version for aggregate fix - -* update ash_sql for include_nil? fix and test it - -* ensure synthesized query aggregates have context set +- [`AshPostgres.DataLayer`] set a proper default for `skip_unique_indexes` ### Improvements: -* include `min_pg_version` in new generators - -* dynamically select and allow setting a repo - -* update ash_sql for latest fixes - -* update ash & ash_sql for various fixes - -* update ash_sql for cleaner queries - -* update ash_sql dependencies for bug fixes - -* prepend `:postgres` to section order - -* pluralize table name in extender - -* update ash/igniter dependencies - -* add `binding()` expression - -* use latest type casting code from ash - -* support new type determination code +- [`mix ash_postgres.install`] include `min_pg_version` in new generators ## [v2.2.0](https://github.com/ash-project/ash_postgres/compare/v2.1.19...v2.2.0) (2024-08-13) From 59d1c3d6cc1a42151e6631a046cc6a7ea252414f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 17 Aug 2024 12:05:33 -0400 Subject: [PATCH 0651/1215] fix: properly handle new igniter installer functions --- lib/mix/tasks/ash_postgres.install.ex | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index ceba0621..959e6c47 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -255,17 +255,20 @@ defmodule Mix.Tasks.AshPostgres.Install do repo, AshPostgres.Igniter.default_repo_contents(otp_app), fn zipper -> - zipper - |> set_otp_app(otp_app) - |> Sourceror.Zipper.top() - |> use_ash_postgres_instead_of_ecto() - |> Sourceror.Zipper.top() - |> remove_adapter_option() - |> Sourceror.Zipper.top() - |> configure_installed_extensions_function() - |> configure_min_pg_version_function() + {:ok, + zipper + |> set_otp_app(otp_app) + |> Sourceror.Zipper.top() + |> use_ash_postgres_instead_of_ecto() + |> Sourceror.Zipper.top() + |> remove_adapter_option()} end ) + |> Igniter.Code.Module.find_and_update_module!( + repo, + &configure_installed_extensions_function/1 + ) + |> Igniter.Code.Module.find_and_update_module!(repo, &configure_min_pg_version_function/1) end defp use_ash_postgres_instead_of_ecto(zipper) do From 73a070e2feccc0969504c84ae552be5ce39b4e62 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 17 Aug 2024 12:09:09 -0400 Subject: [PATCH 0652/1215] chore: release version v2.2.2 --- CHANGELOG.md | 6 ++++++ mix.exs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8448f29..8a3cb2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.2.2](https://github.com/ash-project/ash_postgres/compare/v2.2.1...v2.2.2) (2024-08-17) + +### Bug Fixes: + +- [`mix ash_postgres.install`] properly handle new igniter installer functions + ## [v2.2.1](https://github.com/ash-project/ash_postgres/compare/v2.2.0...v2.2.1) (2024-08-16) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 4ac3c497..dbf45eaa 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.2.1" + @version "2.2.2" def project do [ From b437e3f147fe56f517a259ad7b685c8db1e67129 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 18 Aug 2024 07:43:06 -0400 Subject: [PATCH 0653/1215] fix: `mix ash_postgres.install` not adding ash_functions/min_pg_version --- lib/mix/tasks/ash_postgres.install.ex | 57 +++++++++++---------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 959e6c47..66f2336e 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -319,52 +319,43 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp configure_installed_extensions_function(zipper) do - case Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo) do + case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do {:ok, zipper} -> - case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do + case Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do {:ok, zipper} -> - case Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do - {:ok, zipper} -> - Igniter.Code.List.append_new_to_list(zipper, "ash-functions") + Igniter.Code.List.append_new_to_list(zipper, "ash-functions") - :error -> - {:error, "installed_extensions/0 doesn't return a list"} - end - - _ -> - {:ok, - Igniter.Code.Common.add_code(zipper, """ - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] - end - """)} + :error -> + {:error, "installed_extensions/0 doesn't return a list"} end _ -> - {:ok, zipper} + IO.inspect("HERE!") + + {:ok, + Igniter.Code.Common.add_code(zipper, """ + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """)} + |> IO.inspect() end end defp configure_min_pg_version_function(zipper) do - case Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo) do + case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do {:ok, zipper} -> - case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do - {:ok, zipper} -> - {:ok, zipper} - - _ -> - {:ok, - Igniter.Code.Common.add_code(zipper, """ - def min_pg_version do - # Adjust this according to your postgres version - %Version{major: 16, minor: 0, patch: 0} - end - """)} - end + {:ok, zipper} _ -> - {:ok, zipper} + {:ok, + Igniter.Code.Common.add_code(zipper, """ + def min_pg_version do + # Adjust this according to your postgres version + %Version{major: 16, minor: 0, patch: 0} + end + """)} end end end From 555063d2708544e9c05c9f0f4e191ff92ae2e55c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 18 Aug 2024 07:45:39 -0400 Subject: [PATCH 0654/1215] chore: release version v2.2.3 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3cb2d8..d6056e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.2.3](https://github.com/ash-project/ash_postgres/compare/v2.2.2...v2.2.3) (2024-08-18) + + + + +### Bug Fixes: + +* `mix ash_postgres.install` not adding ash_functions/min_pg_version + ## [v2.2.2](https://github.com/ash-project/ash_postgres/compare/v2.2.1...v2.2.2) (2024-08-17) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index dbf45eaa..0a49d43e 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.2.2" + @version "2.2.3" def project do [ From 2c5c759a20904ab7b214d66ad590895918d29a33 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 18 Aug 2024 07:46:11 -0400 Subject: [PATCH 0655/1215] docs: update readme --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6056e26..18547dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,9 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.2.3](https://github.com/ash-project/ash_postgres/compare/v2.2.2...v2.2.3) (2024-08-18) - - - ### Bug Fixes: -* `mix ash_postgres.install` not adding ash_functions/min_pg_version +- [`mix ash_postgres.install`] was not adding ash_functions/min_pg_version ## [v2.2.2](https://github.com/ash-project/ash_postgres/compare/v2.2.1...v2.2.2) (2024-08-17) From cb172d274c564bb619e8ce56f1342eb7b2f91dfb Mon Sep 17 00:00:00 2001 From: Trond A Ekseth Date: Thu, 22 Aug 2024 15:05:53 +0200 Subject: [PATCH 0656/1215] docs: Output valid example code in ValidateIdentityIndexNames (#373) --- lib/verifiers/validate_identity_index_names.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index f09b95e9..269e881e 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -49,7 +49,7 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do Please configure an index name for this identity in the `identity_index_names` configuration. For example: postgres do - identity_index_names #{inspect(identity.name)}: "a_shorter_name" + identity_index_names #{identity.name}: "a_shorter_name" end """ end From 1d8c2ae0081dd612fc9ab443969e6f04ac578c48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:35:26 -0400 Subject: [PATCH 0657/1215] chore(deps): bump igniter in the production-dependencies group (#372) Bumps the production-dependencies group with 1 update: [igniter](https://github.com/ash-project/igniter). Updates `igniter` from 0.3.19 to 0.3.22 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.19...v0.3.22) --- updated-dependencies: - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 23427c91..ab1c1403 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.19", "dfa4a05e94be72e9b75c9ce73b9ce408c9a5fe8f1f010eb36e711e70da698b46", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "f9b1ca6d11a7dcd1beb0bc0a1c08b8ce1fb4eb140a763037f3b79d9170cbf958"}, + "igniter": {:hex, :igniter, "0.3.22", "342235c729ec7407ca9a7cf88cbaf7ea7568e5f0ec850e76a1b90c5e545d41d0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "db1dd4551ff8cf357581e53d64b6bd3f60a96e9032fcde1e860e0f4c6cf2bdc0"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 1e286106e6788946ee500ee7badcf2f50075ef81 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 25 Aug 2024 17:41:05 -0400 Subject: [PATCH 0658/1215] fix: properly traverse newtypes when determining types closes #1406 --- lib/sql_implementation.ex | 33 ++++++++++++++++++++++----------- test/filter_test.exs | 8 ++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 71ec3cf2..5b9a247b 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -191,6 +191,10 @@ defmodule AshPostgres.SqlImplementation do end end + # def parameterized_type(Ash.Type.NewType, new_type_constraints, no_maps?) do + # constraints = Ash.Type.NewType.constraints(new_type_constraints) + # end + def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) end @@ -213,19 +217,26 @@ defmodule AshPostgres.SqlImplementation do def parameterized_type(type, constraints, no_maps?) do if Ash.Type.ash_type?(type) do - cast_in_query? = - if function_exported?(Ash.Type, :cast_in_query?, 2) do - Ash.Type.cast_in_query?(type, constraints) - else - Ash.Type.cast_in_query?(type) - end + if Ash.Type.NewType.new_type?(type) do + subtype = Ash.Type.NewType.subtype_of(type) + subtype_constraints = Ash.Type.NewType.constraints(type, constraints) - if cast_in_query? do - type = Ash.Type.ecto_type(type) - - parameterized_type(type, constraints, no_maps?) + parameterized_type(subtype, subtype_constraints, no_maps?) else - nil + cast_in_query? = + if function_exported?(Ash.Type, :cast_in_query?, 2) do + Ash.Type.cast_in_query?(type, constraints) + else + Ash.Type.cast_in_query?(type) + end + + if cast_in_query? do + type = Ash.Type.ecto_type(type) + + parameterized_type(type, constraints, no_maps?) + else + nil + end end else if is_atom(type) && :erlang.function_exported(type, :type, 1) do diff --git a/test/filter_test.exs b/test/filter_test.exs index cff2dae4..fb36933c 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -21,6 +21,14 @@ defmodule AshPostgres.FilterTest do end end + describe "ci_string argument casting" do + test "it properly casts" do + Post + |> Ash.Query.for_read(:category_matches, %{category: "category"}) + |> Ash.read!() + end + end + describe "invalid uuid" do test "with an invalid uuid, an invalid error is raised" do assert_raise Ash.Error.Invalid, fn -> From 6b3961e5db298c682c2eefa6910cfcae0d8b1fd4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 25 Aug 2024 17:42:32 -0400 Subject: [PATCH 0659/1215] chore: fix tests --- test/support/resources/post.ex | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 399254fa..86b17273 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -49,6 +49,12 @@ defmodule HasNoComments do end end +defmodule CiCategory do + use Ash.Type.NewType, + subtype_of: :ci_string, + constraints: [casing: :upper] +end + defmodule AshPostgres.Test.Post do @moduledoc false use Ash.Resource, @@ -231,6 +237,11 @@ defmodule AshPostgres.Test.Post do filter(expr(title == "foo")) end + read :category_matches do + argument(:category, CiCategory) + filter(expr(category == ^arg(:category))) + end + read :keyset do pagination do keyset?(true) @@ -317,7 +328,7 @@ defmodule AshPostgres.Test.Post do attribute(:datetime, AshPostgres.TimestamptzUsec, public?: true) attribute(:score, :integer, public?: true) attribute(:public, :boolean, public?: true) - attribute(:category, :ci_string, public?: true) + attribute(:category, CiCategory, public?: true) attribute(:type, :atom, default: :sponsored, writable?: false, public?: false) attribute(:price, :integer, public?: true) attribute(:decimal, :decimal, default: Decimal.new(0), public?: true) From 65adf04ee0951c5c684ba21161ddba643c650c04 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Aug 2024 10:04:12 -0400 Subject: [PATCH 0660/1215] fix: ensure default bindings are present on data layer --- lib/data_layer.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 40bc5f52..a62d49d2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -750,7 +750,9 @@ defmodule AshPostgres.DataLayer do @impl true def return_query(query, resource) do - AshSql.Query.return_query(query, resource) + query + |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation) + |> AshSql.Query.return_query(query, resource) end @impl true From 1c18bc6227f28f795ccc87e2d16bb7458d77651f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Aug 2024 10:45:33 -0400 Subject: [PATCH 0661/1215] chore: fix ash_sql call --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a62d49d2..57243028 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -752,7 +752,7 @@ defmodule AshPostgres.DataLayer do def return_query(query, resource) do query |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation) - |> AshSql.Query.return_query(query, resource) + |> AshSql.Query.return_query(resource) end @impl true From cf1207dd60df66e0db6cef632d5e3ea6bee7dce0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Aug 2024 11:10:54 -0400 Subject: [PATCH 0662/1215] chore: fix recent type casting logic change --- lib/sql_implementation.ex | 56 ++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 5b9a247b..67819a40 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -191,10 +191,6 @@ defmodule AshPostgres.SqlImplementation do end end - # def parameterized_type(Ash.Type.NewType, new_type_constraints, no_maps?) do - # constraints = Ash.Type.NewType.constraints(new_type_constraints) - # end - def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) end @@ -217,39 +213,39 @@ defmodule AshPostgres.SqlImplementation do def parameterized_type(type, constraints, no_maps?) do if Ash.Type.ash_type?(type) do - if Ash.Type.NewType.new_type?(type) do - subtype = Ash.Type.NewType.subtype_of(type) - subtype_constraints = Ash.Type.NewType.constraints(type, constraints) - - parameterized_type(subtype, subtype_constraints, no_maps?) - else - cast_in_query? = - if function_exported?(Ash.Type, :cast_in_query?, 2) do - Ash.Type.cast_in_query?(type, constraints) - else - Ash.Type.cast_in_query?(type) - end - - if cast_in_query? do - type = Ash.Type.ecto_type(type) - - parameterized_type(type, constraints, no_maps?) + cast_in_query? = + if function_exported?(Ash.Type, :cast_in_query?, 2) do + Ash.Type.cast_in_query?(type, constraints) else - nil + Ash.Type.cast_in_query?(type) end + + if cast_in_query? do + type = Ash.Type.ecto_type(type) + + parameterized_type(type, constraints, no_maps?) + else + nil end else if is_atom(type) && :erlang.function_exported(type, :type, 1) do - type = - if type == :ci_string do - :citext - else - type - end + if type == :ci_string do + :citext + else + case type.type(constraints || []) do + :ci_string -> + parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) - Ecto.ParameterizedType.init(type, constraints || []) + _ -> + Ecto.ParameterizedType.init(type, constraints || []) + end + end else - type + if type == :ci_string do + :citext + else + type + end end end end From 4ccdc7b0b60f4af1fc7189841c65ce8c1e31e35f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Aug 2024 11:16:40 -0400 Subject: [PATCH 0663/1215] chore: update deps, fix tests --- mix.lock | 6 +++--- test/support/resources/post.ex | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index ab1c1403..c88e2304 100644 --- a/mix.lock +++ b/mix.lock @@ -9,7 +9,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.12.1", "626765f7066589de6fa09e0876a253ff60c3d00870dd3a1cd696e2ba67bfceea", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df0045ab9d87be947228e05a8d153f3e06e0d05ab10c3b3cc557d2f7243d1940"}, + "ecto": {:hex, :ecto, "3.12.2", "bae2094f038e9664ce5f089e5f3b6132a535d8b018bd280a485c2f33df5c0ce1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e67c70f3a71c6afe80d946d3ced52ecc57c53c9829791bfff1830ff5a1f0c"}, "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, @@ -38,12 +38,12 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.21", "b343f3488b5a986ad38d15ac124b9111b8f63f953e3a6ef3fad44fe129b7fad6", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "45b05c52a1afe4858e10b5e6b8cd33bff3cf0098afc144146c5ff05002d75a9d"}, + "spark": {:hex, :spark, "2.2.22", "abb5ba74ed8b8a69f8d3112fe0d74a1dea261664d9a3bcaf2d0f94f9ee7102f6", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "98b6ea8c19fe97b2b7b20be034ae6cf34e98b03ecba8b7d5a4cc2449f60f3f5e"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 86b17273..8b7fff09 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -51,8 +51,7 @@ end defmodule CiCategory do use Ash.Type.NewType, - subtype_of: :ci_string, - constraints: [casing: :upper] + subtype_of: :ci_string end defmodule AshPostgres.Test.Post do From 725e14ab62229b6f34673102e15e5dc525351249 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 Aug 2024 12:14:13 -0400 Subject: [PATCH 0664/1215] test: add tests for filter input parsing --- test/rel_with_parent_filter_test.exs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs index ef8a3846..20a4082e 100644 --- a/test/rel_with_parent_filter_test.exs +++ b/test/rel_with_parent_filter_test.exs @@ -24,6 +24,34 @@ defmodule AshPostgres.RelWithParentFilterTest do |> Ash.read_one!() end + test "filter constructed from input on relationship using parent works as expected" do + %{id: author_id} = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + %{id: author2_id} = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John"}) + |> Ash.create!() + + filter = + Ash.Filter.parse_input!(Author, %{ + "authors_with_same_first_name" => %{ + "id" => %{ + "eq" => author_id + } + } + }) + + # here we get the expected result of 1 because it is done in the same query + assert %{id: ^author2_id} = + Author + |> Ash.Query.for_read(:read) + |> Ash.Query.do_filter(filter) + |> Ash.read_one!(authorize?: true) + end + test "filter on relationship using parent works as expected when loading relationship" do %{id: author_id} = Author From fe1651169b1d9a48b9e3f27e18582187ad88d6ae Mon Sep 17 00:00:00 2001 From: Shankar Dhanasekaran Date: Wed, 28 Aug 2024 17:35:18 +0530 Subject: [PATCH 0665/1215] Remove IO.inspect (#376) --- lib/mix/tasks/ash_postgres.install.ex | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 66f2336e..e7a44ed8 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -330,8 +330,6 @@ defmodule Mix.Tasks.AshPostgres.Install do end _ -> - IO.inspect("HERE!") - {:ok, Igniter.Code.Common.add_code(zipper, """ def installed_extensions do @@ -339,7 +337,6 @@ defmodule Mix.Tasks.AshPostgres.Install do ["ash-functions"] end """)} - |> IO.inspect() end end From c104e6a2ba0608c318f15c3bf57ce3a33ccb386d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:01:42 -0400 Subject: [PATCH 0666/1215] chore(deps): bump the production-dependencies group with 2 updates (#377) --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index c88e2304..be006432 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.1", "14bfccd4c1e7c17db5aed1ecb5062875f55b56b67f6fba911f3a8ef6739f3cfd", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1e3127e0af0698e652a6bbfb4d4f1a3bb8a48fb42833f4e8f00eda8f1a93082b"}, - "ash_sql": {:hex, :ash_sql, "0.2.30", "244a2071c7fdaec486a186136e52a635dc01f2c1db774d3c506562b339efcf1c", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e93f704b5b29caf0793dd880ec10b1fd313cc0e0a385074955ca36d8af530e4e"}, + "ash_sql": {:hex, :ash_sql, "0.2.31", "721521e073d706169ebb0e68535422c1920580b29829fe949fb679c8674a9691", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e5f578be31f5fa5af8dd1cb27b01b7b1864ef1414472293ce3a4851290cb69b1"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.22", "342235c729ec7407ca9a7cf88cbaf7ea7568e5f0ec850e76a1b90c5e545d41d0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "db1dd4551ff8cf357581e53d64b6bd3f60a96e9032fcde1e860e0f4c6cf2bdc0"}, + "igniter": {:hex, :igniter, "0.3.24", "791a91650ffab9d66b9a3011c66491f767577ad55c363f820cc188554207ee6f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "2e1d336534c6129bae0db043fae650303b96974c0488c290191d6d4c61ec9a9f"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 06c0c328df447053f3346e0c1029e5ec49505bdb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 Aug 2024 18:13:15 -0400 Subject: [PATCH 0667/1215] test: add test for bugfix in ash --- test/bulk_update_test.exs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index e0bb1737..afe8fbd8 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -106,6 +106,8 @@ defmodule AshPostgres.BulkUpdateTest do test "bulk updates honor update action filters when streaming" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) + Logger.configure(level: :debug) + Post |> Ash.bulk_update!(:update_only_freds, %{}, strategy: :stream, @@ -113,9 +115,18 @@ defmodule AshPostgres.BulkUpdateTest do atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")} ) - titles = + posts = Post |> Ash.read!() + + fred = + posts + |> Enum.find(&(&1.title == "fred_stuff")) + + assert fred.created_at != fred.updated_at + + titles = + posts |> Enum.map(& &1.title) |> Enum.sort() From 96508522380e217f6bb33c53d36b08e2b45aac97 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 31 Aug 2024 16:44:05 -0400 Subject: [PATCH 0668/1215] test: additional tests for bulk updates --- test/bulk_update_test.exs | 2 -- test/update_test.exs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/update_test.exs diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index afe8fbd8..6ed482df 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -106,8 +106,6 @@ defmodule AshPostgres.BulkUpdateTest do test "bulk updates honor update action filters when streaming" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Logger.configure(level: :debug) - Post |> Ash.bulk_update!(:update_only_freds, %{}, strategy: :stream, diff --git a/test/update_test.exs b/test/update_test.exs new file mode 100644 index 00000000..59897db3 --- /dev/null +++ b/test/update_test.exs @@ -0,0 +1,37 @@ +defmodule AshPostgres.UpdateTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Post + require Ash.Query + + test "can update with nested maps" do + Post + |> Ash.Changeset.for_create(:create, %{stuff: %{foo: %{bar: :baz}}}) + |> Ash.create!() + |> then(fn record -> + Ash.Query.filter(Post, id == ^record.id) + end) + |> Ash.bulk_update( + :update, + %{ + stuff: %{ + summary: %{ + chat_history: [ + %{"content" => "Default system prompt", "role" => "system"}, + %{ + "content" => + "Here's a collection of tweets from the Twitter list 'Web 3 Mid':\nTweet by KhanAbbas201 (2024-08-26 19:40:13.000000Z):\n@Rahatcodes told me I look good wearing the Eth shirt. https://t.co/xBugAt2tDi\nTweet by Rahatcodes (2024-08-26 19:42:55.000000Z):\n@KhanAbbas201 I dont recall saying this\nTweet by KhanAbbas201 (2024-08-26 19:44:08.000000Z):\n@Rahatcodes Damn what happened to your memory bruv?\nTweet by angelinarusse (2024-08-26 19:56:05.000000Z):\n@dabit3 Real degens call it Twitter\nTweet by angelinarusse (2024-08-26 20:13:58.000000Z):\n@hamseth They tried the same in Afghanistan and it didn’t go well for them.\nTweet by KhanAbbas201 (2024-08-26 20:39:53.000000Z):\nTweet by Osh_mahajan (2024-08-26 21:34:08.000000Z):\n@FedericoNoemie 🐾🐾🦘\nTweet by developer_dao (2024-08-26 21:39:59.000000Z):\n@ZwigoZwitscher @ArweaveEco @k4yls Wildly high praise, ty ty. @k4yls is 🔥 with a 🐶\nTweet by developer_dao (2024-08-26 21:41:17.000000Z):\nRT @ZwigoZwitscher : I've been into @ArweaveEco for years – as an interested outsider – and still learned new things in that course 👇. Thanks…\nTweet by angelin...eet by developer_dao (2024-08-26 22:18:09.000000Z):\nRT @jeremykauffman: BREAKING: France has arrested Gonzalve Bich, the CEO of Bic\nTweet by PatrickAlphaC (2024-08-26 22:26:16.000000Z):\n@oxfav @ar_io_network\n", + "role" => "user" + }, + %{"content" => "test", "role" => "user"}, + %{ + "content" => + "It looks like you're testing the feature. How can I assist you further?", + "role" => "assistant" + } + ] + } + } + } + ) + end +end From 7285b3382eb0fb905385326de13a096e20f43f3f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Sep 2024 01:57:31 -0400 Subject: [PATCH 0669/1215] chore: release version v2.2.4 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18547dc7..275c7b75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.2.4](https://github.com/ash-project/ash_postgres/compare/v2.2.3...v2.2.4) (2024-09-03) + + + + +### Bug Fixes: + +* ensure default bindings are present on data layer + +* properly traverse newtypes when determining types + ## [v2.2.3](https://github.com/ash-project/ash_postgres/compare/v2.2.2...v2.2.3) (2024-08-18) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 0a49d43e..267dace4 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.2.3" + @version "2.2.4" def project do [ From 2015aa658cacfbac181ef8f8fbd9ae81a9a9f02b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Sep 2024 18:02:14 -0400 Subject: [PATCH 0670/1215] improvement: support ash main upsert_condition logic --- lib/data_layer.ex | 3 ++- lib/sql_implementation.ex | 27 +++++++++++++++++++ test/bulk_create_test.exs | 47 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 6 +++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 57243028..200ee3af 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1930,7 +1930,8 @@ defmodule AshPostgres.DataLayer do end) end - defp get_source_for_upsert_field(field, resource) do + @doc false + def get_source_for_upsert_field(field, resource) do case Ash.Resource.Info.attribute(resource, field) do %{source: source} when not is_nil(source) -> source diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 67819a40..d01c134e 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -41,6 +41,33 @@ defmodule AshPostgres.SqlImplementation do {:ok, Ecto.Query.dynamic(fragment("'[]'::jsonb")), acc} end + def expr( + query, + %Ash.Query.UpsertConflict{attribute: attribute}, + _bindings, + _embedded?, + acc, + _type + ) do + query.__ash_bindings__.resource + + {:ok, + Ecto.Query.dynamic( + [], + fragment( + "EXCLUDED.?", + literal( + ^to_string( + AshPostgres.DataLayer.get_source_for_upsert_field( + attribute, + query.__ash_bindings__.resource + ) + ) + ) + ) + ), acc} + end + def expr(query, %AshPostgres.Functions.Binding{}, _bindings, _embedded?, acc, _type) do binding = AshSql.Bindings.get_binding( diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index bdfdd497..d7a2c544 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -2,6 +2,8 @@ defmodule AshPostgres.BulkCreateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Post, Record} + import Ash.Expr + describe "bulk creates" do test "bulk creates insert each input" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) @@ -109,6 +111,51 @@ defmodule AshPostgres.BulkCreateTest do end) end + test "bulk upsert skips with upsert_condition" do + assert [ + {:ok, %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}}, + {:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}}, + {:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}} + ] = + Ash.bulk_create!( + [ + %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}, + %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}, + %{title: "herbert", uniq_if_contains_foo: "3", price: 30} + ], + Post, + :create, + return_stream?: true, + return_records?: true + ) + |> Enum.sort_by(fn {:ok, result} -> result.title end) + + assert [ + {:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000}}, + {:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}} + ] = + Ash.bulk_create!( + [ + %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}, + %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000}, + %{title: "herbert", uniq_if_contains_foo: "3", price: 30} + ], + Post, + :upsert_with_no_filter, + return_stream?: true, + upsert_condition: expr(price != upsert_conflict(:price)), + return_errors?: true, + return_records?: true + ) + |> Enum.sort_by(fn + {:ok, result} -> + result.title + + _ -> + nil + end) + end + # confirmed that this doesn't work because it can't. An upsert must map to a potentially successful insert. # leaving this test here for posterity # test "bulk creates can upsert with id" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 8b7fff09..e4f64c23 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -278,6 +278,12 @@ defmodule AshPostgres.Test.Post do end) end + create :upsert_with_no_filter do + upsert?(true) + upsert_identity(:uniq_if_contains_foo) + upsert_fields([:price]) + end + update :set_title_from_author do change(atomic_update(:title, expr(author.first_name))) end From 6e0b8fb0358d53fe8e1cb331d7da4d14be940507 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Sep 2024 22:26:46 -0400 Subject: [PATCH 0671/1215] chore: release version v2.2.5 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 275c7b75..c63f6d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.2.5](https://github.com/ash-project/ash_postgres/compare/v2.2.4...v2.2.5) (2024-09-04) + + + + +### Improvements: + +* support ash main upsert_condition logic + ## [v2.2.4](https://github.com/ash-project/ash_postgres/compare/v2.2.3...v2.2.4) (2024-09-03) diff --git a/mix.exs b/mix.exs index 267dace4..974223c3 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.2.4" + @version "2.2.5" def project do [ From 607254d3184e39ab255b2ccd75cecf6e8cc2850d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Sep 2024 22:27:30 -0400 Subject: [PATCH 0672/1215] chore: update changelog --- CHANGELOG.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c63f6d5e..be827fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,23 +7,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.2.5](https://github.com/ash-project/ash_postgres/compare/v2.2.4...v2.2.5) (2024-09-04) - - - ### Improvements: -* support ash main upsert_condition logic +- [`AshPostgres.DataLayer`] support ash main upsert_condition logic ## [v2.2.4](https://github.com/ash-project/ash_postgres/compare/v2.2.3...v2.2.4) (2024-09-03) - - - ### Bug Fixes: -* ensure default bindings are present on data layer +- [`AshPostgres.DataLayer`] ensure default bindings are present on data layer -* properly traverse newtypes when determining types +- [`AshPostgres.DataLayer`] properly traverse newtypes when determining types ## [v2.2.3](https://github.com/ash-project/ash_postgres/compare/v2.2.2...v2.2.3) (2024-08-18) From 0ccb35a713b9097c4aac6fde996dbb4d1c00cccb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Sep 2024 22:42:57 -0400 Subject: [PATCH 0673/1215] chore: update ash --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 974223c3..63d080c4 100644 --- a/mix.exs +++ b/mix.exs @@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.3")}, + {:ash, ash_version("~> 3.4 and >= 3.4.2")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index be006432..ea609ef7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.1", "14bfccd4c1e7c17db5aed1ecb5062875f55b56b67f6fba911f3a8ef6739f3cfd", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.8 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1e3127e0af0698e652a6bbfb4d4f1a3bb8a48fb42833f4e8f00eda8f1a93082b"}, + "ash": {:hex, :ash, "3.4.2", "17544d04a1ed5fdc7fc61e9fbb491418c322c73c9cb0c00836c0f80070d4e09a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df5a851797a82a7fa9a1348a0c79bf206f77ce1e3047ec00ce4bfdf733e9459d"}, "ash_sql": {:hex, :ash_sql, "0.2.31", "721521e073d706169ebb0e68535422c1920580b29829fe949fb679c8674a9691", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e5f578be31f5fa5af8dd1cb27b01b7b1864ef1414472293ce3a4851290cb69b1"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -38,7 +38,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.22", "abb5ba74ed8b8a69f8d3112fe0d74a1dea261664d9a3bcaf2d0f94f9ee7102f6", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "98b6ea8c19fe97b2b7b20be034ae6cf34e98b03ecba8b7d5a4cc2449f60f3f5e"}, + "spark": {:hex, :spark, "2.2.23", "78f0a1b0b713a91ad556fe9dc19ec92d977aaa0803cce2e255d90e58b9045c2a", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a354b5cd7c3f021e3cd1da5a033b7643fe7b3c71c96b96d9f500a742f40c94db"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 133be5094b9946ade9d1f8c8b905456f7227f81a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Sep 2024 20:28:45 -0400 Subject: [PATCH 0674/1215] feat: `mix ash_postgres.gen.resources` --- .../set-up-with-existing-database.md | 33 + lib/igniter.ex | 24 + .../migration_generator.ex | 1 + lib/mix/tasks/ash_postgres.gen.resources.ex | 137 +++ .../tasks/ash_postgres.generate_migrations.ex | 6 +- lib/resource_generator/resource_generator.ex | 658 +++++++++++ lib/resource_generator/sensitive_data.ex | 73 ++ lib/resource_generator/spec.ex | 1026 +++++++++++++++++ mix.exs | 2 + mix.lock | 12 +- .../schematic_groups/20240821213522.json | 29 + test/support/domain.ex | 9 + 12 files changed, 2002 insertions(+), 8 deletions(-) create mode 100644 documentation/tutorials/set-up-with-existing-database.md create mode 100644 lib/mix/tasks/ash_postgres.gen.resources.ex create mode 100644 lib/resource_generator/resource_generator.ex create mode 100644 lib/resource_generator/sensitive_data.ex create mode 100644 lib/resource_generator/spec.ex create mode 100644 priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json diff --git a/documentation/tutorials/set-up-with-existing-database.md b/documentation/tutorials/set-up-with-existing-database.md new file mode 100644 index 00000000..0c5d92c0 --- /dev/null +++ b/documentation/tutorials/set-up-with-existing-database.md @@ -0,0 +1,33 @@ +# Setting AshPostgres up with an existing database + +If you already have a postgres database and you'd like to get +started quickly, you can scaffold resources directly from your +database. + +First, create an application with AshPostgres if you haven't already: + +```bash +mix igniter.new my_app + --install ash,ash_postgres + --with phx.new # add this if you will be using phoenix too +``` + +Then, go into your `config/dev.exs` and configure your repo to use +your existing database. + +Finally, run: + +```bash +mix ash_postgres.gen.resources MyApp.MyDomain --tables table1,table2,table3 +``` + +## More fine grained control + +You may want to do multiple passes to separate your application into multiple domains. For example: + +```bash +mix ash_postgres.gen.resources MyApp.Accounts --tables users,roles,tokens +mix ash_postgres.gen.resources MyApp.Blog --tables posts,comments +``` + +See the docs for `mix ash_postgres.gen.resources` for more information. diff --git a/lib/igniter.ex b/lib/igniter.ex index c7140277..662418e0 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -18,6 +18,30 @@ defmodule AshPostgres.Igniter do """ end + def table(igniter, resource) do + igniter + |> Spark.Igniter.get_option(resource, [:postgres, :table]) + |> case do + {igniter, {:ok, value}} when is_binary(value) or is_nil(value) -> + {:ok, igniter, value} + + _ -> + :error + end + end + + def repo(igniter, resource) do + igniter + |> Spark.Igniter.get_option(resource, [:postgres, :repo]) + |> case do + {igniter, {:ok, value}} when is_atom(value) -> + {:ok, igniter, value} + + _ -> + :error + end + end + def add_postgres_extension(igniter, repo_name, extension) do Igniter.Code.Module.find_and_update_module!(igniter, repo_name, fn zipper -> case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index ff8f1d41..7b9de7a2 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -18,6 +18,7 @@ defmodule AshPostgres.MigrationGenerator do format: true, dry_run: false, check: false, + snapshots_only: false, dont_drop_columns: false def generate(domains, opts \\ []) do diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex new file mode 100644 index 00000000..4bb2cd05 --- /dev/null +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -0,0 +1,137 @@ +defmodule Mix.Tasks.AshPostgres.Gen.Resources do + use Igniter.Mix.Task + + @example "mix ash_postgres.gen.resource MyApp.MyDomain" + + @shortdoc "Generates or updates resources based on a database schema" + + @doc """ + #{@shortdoc} + + ## Example + + `#{@example}` + + ## Domain + + The domain will be generated if it does not exist. If you aren't sure, + we suggest using something like `MyApp.App`. + + ## Options + + - `repo`, `r` - The repo or repos to generate resources for, comma separated. Can be specified multiple times. Defaults to all repos. + - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. + - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. + - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. + + ## Tables + + When specifying tables to include with `--tables`, you can specify the table name, or the schema and table name separated by a period. + For example, `users` will generate resources for the `users` table in the `public` schema, but `accounts.users` will generate resources for the `users` table in the `accounts` schema. + + To include all tables in a given schema, add a period only with no table name, i.e `schema.`, i.e `accounts.`. + + When skipping tables with `--skip-tables`, the same rules apply, except that the `schema.` format is not supported. + """ + + @impl Igniter.Mix.Task + def info(_argv, _parent) do + %Igniter.Mix.Task.Info{ + positional: [:domain], + example: @example, + schema: [ + repo: :keep, + tables: :keep, + skip_tables: :keep, + snapshots_only: :boolean, + domain: :keep + ], + aliases: [ + t: :tables, + r: :repo, + d: :domain, + s: :skip_tables + ] + } + end + + @impl Igniter.Mix.Task + def igniter(igniter, argv) do + Mix.Task.run("compile") + + {%{domain: domain}, argv} = positional_args!(argv) + + domain = Igniter.Code.Module.parse(domain) + + options = options!(argv) + + repos = + options[:repo] || + Mix.Project.config()[:app] + |> Application.get_env(:ecto_repos, []) + + case repos do + [] -> + igniter + |> Igniter.add_warning("No ecto repos configured.") + + repos -> + Mix.shell().info("Generating resources from #{inspect(repos)}") + + prompt = + """ + + Would you like to generate migrations for the current structure? (recommended) + + If #{IO.ANSI.green()}yes#{IO.ANSI.reset()}: + We will generate migrations based on the generated resources. + You should then change your database name in your config, and + run `mix ash.setup`. + + If you already have ecto migrations you'd like to use, run + this command with `--snapshots-only`, in which case only resource + snapshots will be generated. + #{IO.ANSI.green()} + Going forward, your resources will be the source of truth.#{IO.ANSI.reset()} + #{IO.ANSI.red()} + *WARNING* + + If you run `mix ash.reset` after this command without updating + your config, you will be *deleting the database you just used to + generate these resources*!#{IO.ANSI.reset()} + + If #{IO.ANSI.red()}no#{IO.ANSI.reset()}: + + We will not generate any migrations. This means you have migrations already that + can get you from zero to the current starting point. + #{IO.ANSI.yellow()} + You will have to hand-write migrations from this point on.#{IO.ANSI.reset()} + """ + + options = + if Mix.shell().yes?(prompt) do + Keyword.put(options, :no_migrations, false) + else + Keyword.put(options, :no_migrations, true) + end + + migration_opts = + if options[:snapshots_only] do + ["--snapshots-only"] + else + [] + end + + igniter + |> Igniter.compose_task("ash.gen.domain", [inspect(domain), "--ignore-if-exists"]) + |> AshPostgres.ResourceGenerator.generate(repos, domain, options) + |> then(fn igniter -> + if options[:no_migrations] do + igniter + else + Igniter.add_task(igniter, "ash_postgres.generate_migrations", migration_opts) + end + end) + end + end +end diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 2c31d0f7..68f92b55 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -21,6 +21,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do * `no-format` - files that are created will not be formatted with the code formatter * `dry-run` - no files are created, instead the new migration is printed * `check` - no files are created, returns an exit(1) code if the current snapshots and resources don't fit + * `snapshots-only` - no migrations are generated, only snapshots are stored #### Snapshots @@ -90,6 +91,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do migration_path: :string, tenant_migration_path: :string, quiet: :boolean, + snapshots_only: :boolean, name: :string, no_format: :boolean, dry_run: :boolean, @@ -100,7 +102,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do domains = AshPostgres.Mix.Helpers.domains!(opts, args, false) - if Enum.empty?(domains) do + if Enum.empty?(domains) && !opts[:snapshots_only] do IO.warn(""" No domains found, so no resource-related migrations will be generated. Pass the `--domains` option or configure `config :your_app, ash_domains: [...]` @@ -113,7 +115,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do |> Keyword.delete(:no_format) |> Keyword.put_new(:name, name) - if !opts[:name] && !opts[:dry_run] && !opts[:check] do + if !opts[:name] && !opts[:dry_run] && !opts[:check] && !opts[:snapshots_only] do IO.warn(""" Name must be provided when generating migrations, unless `--dry-run` or `--check` is also provided. Using an autogenerated name will be deprecated in a future release. diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex new file mode 100644 index 00000000..9ba9586f --- /dev/null +++ b/lib/resource_generator/resource_generator.ex @@ -0,0 +1,658 @@ +defmodule AshPostgres.ResourceGenerator do + alias AshPostgres.ResourceGenerator.Spec + + require Logger + + def generate(igniter, repos, domain, opts \\ []) do + {igniter, resources} = Ash.Resource.Igniter.list_resources(igniter) + + resources = + Task.async_stream(resources, fn resource -> + {resource, AshPostgres.Igniter.repo(igniter, resource), + AshPostgres.Igniter.table(igniter, resource)} + end) + |> Enum.map(fn {:ok, {resource, repo, table}} -> + repo = + case repo do + {:ok, _igniter, repo} -> repo + _ -> nil + end + + table = + case table do + {:ok, _igniter, table} -> table + _ -> nil + end + + {resource, repo, table} + end) + + igniter = Igniter.include_all_elixir_files(igniter) + + opts = + if opts[:tables] do + Keyword.put( + opts, + :tables, + opts[:tables] + |> List.wrap() + |> Enum.join(",") + |> String.split(",") + ) + else + opts + end + + opts = + if opts[:skip_tables] do + Keyword.put( + opts, + :skip_tables, + opts[:skip_tables] + |> List.wrap() + |> Enum.join(",") + |> String.split(",") + ) + else + opts + end + + specs = + repos + |> Enum.flat_map(&Spec.tables(&1, skip_tables: opts[:skip_tables], tables: opts[:tables])) + |> Enum.map(fn %{table_name: table} = spec -> + resource = + table + |> Macro.camelize() + |> then(&Module.concat([domain, &1])) + + %{spec | resource: resource} + end) + |> Enum.group_by(& &1.resource) + |> Enum.map(fn + {_resource, [single]} -> + single + + {resource, specs} -> + raise """ + Duplicate resource names detected across multiple repos: #{inspect(resource)} + + #{inspect(Enum.map(specs, & &1.repo))} + + To address this, run this command separately for each repo and specify the + `--domain` option to put the resources into a separate domain, or omit the table + with `--tables` or `--skip-tables` + """ + end) + |> Spec.add_relationships(resources) + + Enum.reduce(specs, igniter, fn table_spec, igniter -> + table_to_resource(igniter, table_spec, domain, opts) + end) + end + + defp table_to_resource( + igniter, + %AshPostgres.ResourceGenerator.Spec{} = table_spec, + domain, + opts + ) do + no_migrate_flag = + if opts[:no_migrations] do + "migrate? false" + end + + resource = + """ + use Ash.Resource, + domain: #{inspect(domain)}, + data_layer: AshPostgres.DataLayer + + postgres do + table #{inspect(table_spec.table_name)} + repo #{inspect(table_spec.repo)} + #{no_migrate_flag} + #{references(table_spec, opts[:no_migrations])} + #{custom_indexes(table_spec, opts[:no_migrations])} + #{check_constraints(table_spec, opts[:no_migrations])} + #{skip_unique_indexes(table_spec)} + #{identity_index_names(table_spec)} + end + + attributes do + #{attributes(table_spec)} + end + """ + |> add_identities(table_spec) + |> add_relationships(table_spec) + + igniter + |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) + |> Igniter.Code.Module.create_module(table_spec.resource, resource) + end + + defp check_constraints(%{check_constraints: _check_constraints}, true) do + "" + end + + defp check_constraints(%{check_constraints: []}, _) do + "" + end + + defp check_constraints(%{check_constraints: check_constraints}, _) do + IO.inspect(check_constraints) + + check_constraints = + Enum.map_join(check_constraints, "\n", fn check_constraint -> + """ + check_constraint :#{check_constraint.column}, "#{check_constraint.name}", check: "#{check_constraint.expression}", message: "is invalid" + """ + end) + + """ + check_constraints do + #{check_constraints} + end + """ + end + + defp skip_unique_indexes(%{indexes: indexes}) do + indexes + |> Enum.filter(& &1.unique?) + |> Enum.filter(fn %{columns: columns} -> + Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end) + |> Enum.reject(&index_as_identity?/1) + |> case do + [] -> + "" + + indexes -> + """ + skip_unique_indexes [#{Enum.map_join(indexes, ",", &":#{&1.name}")}] + """ + end + end + + defp identity_index_names(%{indexes: indexes}) do + indexes + |> Enum.filter(& &1.unique?) + |> Enum.filter(fn %{columns: columns} -> + Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end) + |> case do + [] -> + [] + + indexes -> + indexes + |> Enum.map_join(", ", fn index -> + "#{index.name}: \"#{index.name}\"" + end) + |> then(&"identity_index_names [#{&1}]") + end + end + + defp add_identities(str, %{indexes: indexes}) do + indexes + |> Enum.filter(& &1.unique?) + |> Enum.filter(fn %{columns: columns} -> + Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end) + |> Enum.map(fn index -> + name = index.name + + fields = "[" <> Enum.map_join(index.columns, ", ", &":#{&1}") <> "]" + + case identity_options(index) do + "" -> + "identity :#{name}, #{fields}" + + options -> + """ + identity :#{name}, #{fields} do + #{options} + end + """ + end + end) + |> case do + [] -> + str + + identities -> + """ + #{str} + + identities do + #{Enum.join(identities, "\n")} + end + """ + end + end + + defp identity_options(index) do + "" + |> add_identity_where(index) + |> add_nils_distinct?(index) + end + + defp add_identity_where(str, %{where_clause: nil}), do: str + + defp add_identity_where(str, %{name: name, where_clause: where_clause}) do + Logger.warning(""" + Index #{name} has been left commented out in its resource + Manual conversion of `#{where_clause}` to an Ash expression is required. + """) + + """ + #{str} + # Express `#{where_clause}` as an Ash expression + # where expr(...) + """ + end + + defp add_nils_distinct?(str, %{nils_distinct?: false}) do + "#{str}\n nils_distinct? false" + end + + defp add_nils_distinct?(str, _), do: str + + defp add_relationships(str, %{relationships: []}) do + str + end + + defp add_relationships(str, %{relationships: relationships} = spec) do + relationships + |> Enum.map_join("\n", fn relationship -> + case relationship_options(spec, relationship) do + "" -> + "#{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)}" + + options -> + """ + #{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)} do + #{options} + end + """ + end + end) + |> then(fn rels -> + """ + #{str} + + relationships do + #{rels} + end + """ + end) + end + + defp relationship_options(spec, %{type: :belongs_to} = rel) do + case Enum.find(spec.attributes, fn attribute -> + attribute.name == rel.source_attribute + end) do + %{ + default: default, + generated?: generated?, + source: source, + name: name + } + when not is_nil(default) or generated? or source != name -> + "define_attribute? false" + |> add_destination_attribute(rel, "id") + |> add_source_attribute(rel, "#{rel.name}_id") + |> add_allow_nil(rel) + |> add_filter(rel) + + attribute -> + "" + |> add_destination_attribute(rel, "id") + |> add_source_attribute(rel, "#{rel.name}_id") + |> add_allow_nil(rel) + |> add_primary_key(attribute.primary_key?) + |> add_attribute_type(attribute) + |> add_filter(rel) + end + end + + defp relationship_options(_spec, rel) do + default_destination_attribute = + rel.source + |> Module.split() + |> List.last() + |> Macro.underscore() + |> Kernel.<>("_id") + + "" + |> add_destination_attribute(rel, default_destination_attribute) + |> add_source_attribute(rel, "id") + |> add_filter(rel) + end + + defp add_filter(str, %{match_with: []}), do: str + + defp add_filter(str, %{match_with: match_with}) do + filter = + Enum.map_join(match_with, " and ", fn {source, dest} -> + "parent(#{source}) == #{dest}" + end) + + "#{str}\n filter expr(#{filter})" + end + + defp add_attribute_type(str, %{attr_type: :uuid}), do: str + + defp add_attribute_type(str, %{attr_type: attr_type}) do + "#{str}\n attribute_type :#{attr_type}" + end + + defp add_destination_attribute(str, rel, default) do + if rel.destination_attribute == default do + str + else + "#{str}\n destination_attribute :#{rel.destination_attribute}" + end + end + + defp add_source_attribute(str, rel, default) do + if rel.source_attribute == default do + str + else + "#{str}\n source_attribute :#{rel.source_attribute}" + end + end + + defp references(_table_spec, true) do + "" + end + + defp references(table_spec, _) do + table_spec.foreign_keys + |> Enum.flat_map(fn %Spec.ForeignKey{} = foreign_key -> + default_name = "#{table_spec.table_name}_#{foreign_key.column}_fkey" + + if default_name == foreign_key.constraint_name and + foreign_key.on_update == "NO ACTION" and + foreign_key.on_delete == "NO ACTION" and + foreign_key.match_type in ["SIMPLE", "NONE"] do + [] + else + relationship = + Enum.find(table_spec.relationships, fn relationship -> + relationship.type == :belongs_to and + relationship.constraint_name == foreign_key.constraint_name + end).name + + options = + "" + |> add_on(:update, foreign_key.on_update) + |> add_on(:delete, foreign_key.on_delete) + |> add_match_with(foreign_key.match_with) + |> add_match_type(foreign_key.match_type) + + [ + """ + reference :#{relationship} do + #{options} + end + """ + ] + end + |> Enum.join("\n") + |> String.trim() + |> then( + &[ + """ + references do + #{&1} + end + """ + ] + ) + end) + end + + defp add_match_with(str, empty) when empty in [[], nil], do: str + + defp add_match_with(str, keyval), + do: str <> "\nmatch_with [#{Enum.map_join(keyval, fn {key, val} -> "#{key}: :#{val}" end)}]" + + defp add_match_type(str, type) when type in ["SIMPLE", "NONE"], do: str + + defp add_match_type(str, "FULL"), do: str <> "\nmatch_type :full" + defp add_match_type(str, "PARTIAL"), do: str <> "\nmatch_type :partial" + + defp add_on(str, type, "RESTRICT"), do: str <> "\non_#{type} :restrict" + defp add_on(str, type, "CASCADE"), do: str <> "\non_#{type} :#{type}" + defp add_on(str, type, "SET NULL"), do: str <> "\non_#{type} :nilify" + defp add_on(str, _type, _), do: str + + defp custom_indexes(table_spec, true) do + table_spec.indexes + |> Enum.reject(fn index -> + !index.unique? || (&index_as_identity?/1) + end) + |> Enum.reject(fn index -> + Enum.any?(index.columns, &String.contains?(&1, "(")) + end) + |> case do + [] -> + "" + + indexes -> + indexes + |> Enum.map_join(", ", fn %{index: name, columns: columns} -> + columns = Enum.map_join(columns, ", ", &":#{&1}") + "{[#{columns}], #{inspect(name)}}" + end) + |> then(fn index_names -> + "unique_index_names [#{index_names}]" + end) + end + end + + defp custom_indexes(table_spec, _) do + table_spec.indexes + |> Enum.reject(&index_as_identity?/1) + |> case do + [] -> + "" + + indexes -> + indexes + |> Enum.map_join("\n", fn index -> + columns = + index.columns + |> Enum.map_join(", ", fn thing -> + if String.contains?(thing, "(") do + inspect(thing) + else + ":#{thing}" + end + end) + + case index_options(table_spec, index) do + "" -> + "index [#{columns}]" + + options -> + """ + index [#{columns}] do + #{options} + end + """ + end + end) + |> then(fn indexes -> + """ + custom_indexes do + #{indexes} + end + """ + end) + end + end + + defp index_as_identity?(index) do + is_nil(index.where_clause) and index.using == "btree" and index.include in [nil, []] and + Enum.all?(index.columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end + + defp index_options(spec, index) do + default_name = + if Enum.all?(index.columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) do + AshPostgres.CustomIndex.name(spec.table_name, %{fields: index.columns}) + end + + "" + |> add_index_name(index.name, default_name) + |> add_unique(index.unique?) + |> add_using(index.using) + |> add_where(index.where_clause) + |> add_include(index.include) + |> add_nulls_distinct(index.nulls_distinct) + end + + defp add_index_name(str, default, default), do: str + defp add_index_name(str, name, _), do: str <> "\nname #{inspect(name)}" + + defp add_unique(str, false), do: str + defp add_unique(str, true), do: str <> "\nunique true" + + defp add_nulls_distinct(str, true), do: str + defp add_nulls_distinct(str, false), do: str <> "\nnulls_distinct false" + + defp add_using(str, "btree"), do: str + defp add_using(str, using), do: str <> "\nusing #{inspect(using)}" + + defp add_where(str, empty) when empty in [nil, ""], do: str + defp add_where(str, where), do: str <> "\nwhere #{inspect(where)}" + + defp add_include(str, empty) when empty in [nil, []], do: str + + defp add_include(str, include), + do: str <> "\ninclude [#{Enum.map_join(include, ", ", &inspect/1)}]" + + defp attributes(table_spec) do + table_spec.attributes + |> Enum.split_with(& &1.default) + |> then(fn {l, r} -> r ++ l end) + |> Enum.split_with(& &1.primary_key?) + |> then(fn {l, r} -> l ++ r end) + |> Enum.filter(fn attribute -> + if not is_nil(attribute.default) or !!attribute.generated? or + attribute.source != attribute.name do + true + else + not Enum.any?(table_spec.relationships, fn relationship -> + relationship.type == :belongs_to and relationship.source_attribute == attribute.name + end) + end + end) + |> Enum.map_join("\n", &attribute(&1)) + end + + defp attribute(attribute) do + now_default = &DateTime.utc_now/0 + uuid_default = &Ash.UUID.generate/0 + + {constructor, attribute, type?, type_option?} = + case attribute do + %{name: "updated_at", attr_type: attr_type} -> + {"update_timestamp", %{attribute | default: nil, generated?: false}, false, + attr_type != :utc_datetime_usec} + + %{default: default, attr_type: attr_type} + when default == now_default -> + {"create_timestamp", %{attribute | default: nil, generated?: false}, false, + attr_type != :utc_datetime_usec} + + %{default: default, attr_type: attr_type, primary_key?: true} + when default == uuid_default -> + {"uuid_primary_key", + %{attribute | default: nil, primary_key?: false, generated?: false, allow_nil?: true}, + false, attr_type != :uuid} + + _ -> + {"attribute", attribute, true, false} + end + + case String.trim(options(attribute, type_option?)) do + "" -> + if type? do + "#{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)}" + else + "#{constructor} :#{attribute.name}" + end + + options -> + if type? do + """ + #{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)} do + #{options} + end + """ + else + """ + #{constructor} :#{attribute.name} do + #{options} + end + """ + end + end + end + + defp options(attribute, type_option?) do + "" + |> add_primary_key(attribute) + |> add_allow_nil(attribute) + |> add_sensitive(attribute) + |> add_default(attribute) + |> add_type(attribute, type_option?) + |> add_generated(attribute) + |> add_source(attribute) + end + + defp add_type(str, %{attr_type: attr_type}, true) do + str <> "\n type #{inspect(attr_type)}" + end + + defp add_type(str, _, _), do: str + + defp add_generated(str, %{generated?: true}) do + str <> "\n generated? true" + end + + defp add_generated(str, _), do: str + + defp add_source(str, %{name: name, source: source}) when name != source do + str <> "\n source :#{source}" + end + + defp add_source(str, _), do: str + + defp add_primary_key(str, %{primary_key?: true}) do + str <> "\n primary_key? true" + end + + defp add_primary_key(str, _), do: str + + defp add_allow_nil(str, %{allow_nil?: false}) do + str <> "\n allow_nil? false" + end + + defp add_allow_nil(str, _), do: str + + defp add_sensitive(str, %{sensitive?: true}) do + str <> "\n sensitive? true" + end + + defp add_sensitive(str, _), do: str + + defp add_default(str, %{default: default}) when not is_nil(default) do + str <> "\n default #{inspect(default)}" + end + + defp add_default(str, _), do: str +end diff --git a/lib/resource_generator/sensitive_data.ex b/lib/resource_generator/sensitive_data.ex new file mode 100644 index 00000000..10fa3691 --- /dev/null +++ b/lib/resource_generator/sensitive_data.ex @@ -0,0 +1,73 @@ +defmodule AshPostgres.ResourceGenerator.SensitiveData do + # I got this from ChatGPT, but this is a best effort transformation + # anyway. + @sensitive_patterns [ + # Password-related + ~r/password/i, + ~r/passwd/i, + ~r/pass/i, + ~r/pwd/i, + ~r/hash(ed)?(_password)?/i, + + # Authentication-related + ~r/auth(_key)?/i, + ~r/token/i, + ~r/secret(_key)?/i, + ~r/api_key/i, + + # Personal Information + ~r/ssn/i, + ~r/social(_security)?(_number)?/i, + ~r/(credit_?card|cc)(_number)?/i, + ~r/passport(_number)?/i, + ~r/driver_?licen(s|c)e(_number)?/i, + ~r/national_id/i, + + # Financial Information + ~r/account(_number)?/i, + ~r/routing(_number)?/i, + ~r/iban/i, + ~r/swift(_code)?/i, + ~r/tax_id/i, + + # Contact Information + ~r/phone(_number)?/i, + ~r/email(_address)?/i, + ~r/address/i, + + # Health Information + ~r/medical(_record)?/i, + ~r/health(_data)?/i, + ~r/diagnosis/i, + ~r/treatment/i, + + # Biometric Data + ~r/fingerprint/i, + ~r/retina_scan/i, + ~r/face_id/i, + ~r/dna/i, + + # Encrypted or Encoded Data + ~r/encrypt(ed)?/i, + ~r/encoded/i, + ~r/cipher/i, + + # Other Potentially Sensitive Data + ~r/private(_key)?/i, + ~r/confidential/i, + ~r/restricted/i, + ~r/sensitive/i, + + # General patterns + ~r/.*_salt/i, + ~r/.*_secret/i, + ~r/.*_key/i, + ~r/.*_token/i + ] + + def is_sensitive?(column_name) do + Enum.any?(@sensitive_patterns, fn pattern -> + Regex.match?(pattern, column_name) + end) + end +end diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex new file mode 100644 index 00000000..1b73fc41 --- /dev/null +++ b/lib/resource_generator/spec.ex @@ -0,0 +1,1026 @@ +defmodule AshPostgres.ResourceGenerator.Spec do + require Logger + + defstruct [ + :attributes, + :table_name, + :repo, + :resource, + :schema, + check_constraints: [], + foreign_keys: [], + indexes: [], + identities: [], + relationships: [] + ] + + defmodule Attribute do + defstruct [ + :name, + :type, + :attr_type, + :default, + :migration_default, + :size, + :source, + generated?: false, + primary_key?: false, + sensitive?: false, + allow_nil?: true + ] + end + + defmodule ForeignKey do + defstruct [ + :constraint_name, + :match_type, + :column, + :references, + :destination_field, + :on_delete, + :on_update, + :match_with + ] + end + + defmodule Index do + defstruct [:name, :columns, :unique?, :nulls_distinct, :where_clause, :using, :include] + end + + defmodule CheckConstraint do + defstruct [:name, :column, :expression] + end + + defmodule Relationship do + defstruct [ + :name, + :type, + :destination, + :match_with, + :source, + :source_attribute, + :constraint_name, + :destination_attribute, + :allow_nil?, + :foreign_key + ] + end + + def tables(repo, opts \\ []) do + {:ok, result, _} = + Ecto.Migrator.with_repo(repo, fn repo -> + repo + |> table_specs(opts) + |> verify_found_tables(opts) + |> Enum.group_by(&Enum.take(&1, 2), fn [_, _, field, type, default, size, allow_nil?] -> + name = Macro.underscore(field) + + %Attribute{ + name: name, + source: field, + type: type, + migration_default: default, + size: size, + allow_nil?: allow_nil? + } + end) + |> Enum.map(fn {[table_name, table_schema], attributes} -> + attributes = build_attributes(attributes, table_name, table_schema, repo) + + %__MODULE__{ + table_name: table_name, + schema: table_schema, + repo: repo, + attributes: attributes + } + end) + |> Enum.map(fn spec -> + spec + |> add_foreign_keys() + |> add_indexes() + |> add_check_constraints() + end) + end) + + result + end + + defp add_foreign_keys(spec) do + %Postgrex.Result{rows: fkey_rows} = + spec.repo.query!( + """ + SELECT + tc.constraint_name, + rc.match_option AS match_type, + rc.update_rule AS on_update, + rc.delete_rule AS on_delete, + array_agg(DISTINCT kcu.column_name) AS referencing_columns, + array_agg(DISTINCT ccu.column_name) AS referenced_columns, + ccu.table_name AS foreign_table_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + AND ccu.table_schema = tc.table_schema + JOIN information_schema.referential_constraints AS rc + ON tc.constraint_name = rc.constraint_name + AND tc.table_schema = rc.constraint_schema + WHERE + tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = $1 + AND tc.table_schema = $2 + GROUP BY + tc.constraint_name, + ccu.table_name, + rc.match_option, + rc.update_rule, + rc.delete_rule; + """, + [spec.table_name, spec.schema], + log: false + ) + + %{ + spec + | foreign_keys: + Enum.map( + fkey_rows, + fn [ + constraint_name, + match_type, + on_update, + on_delete, + referencing_columns, + referenced_columns, + destination + ] -> + {[column_name], match_with_source} = + Enum.split(referencing_columns, 1) + + {[destination_field], match_with_destination} = + Enum.split(referenced_columns, 1) + + %ForeignKey{ + constraint_name: constraint_name, + column: column_name, + references: destination, + destination_field: destination_field, + on_delete: on_delete, + on_update: on_update, + match_type: match_type, + match_with: Enum.zip(match_with_source, match_with_destination) + } + end + ) + } + end + + defp add_check_constraints(spec) do + %Postgrex.Result{rows: check_constraint_rows} = + spec.repo.query!( + """ + SELECT + conname AS constraint_name, + pg_get_constraintdef(oid) AS constraint_definition + FROM + pg_constraint + WHERE + contype = 'c' + AND conrelid::regclass::text = $1 + """, + [spec.table_name] + ) + + attribute = Enum.find(spec.attributes, & &1.primary_key?) || Enum.at(spec.attributes, 0) + + spec + |> Map.put( + :check_constraints, + Enum.flat_map(check_constraint_rows, fn + [name, "CHECK " <> expr] -> + [ + %CheckConstraint{ + name: name, + column: attribute.source, + expression: expr + } + ] + + _ -> + [] + end) + ) + end + + defp add_indexes(spec) do + %Postgrex.Result{rows: index_rows} = + spec.repo.query!( + """ + SELECT + i.relname AS index_name, + ix.indisunique AS is_unique, + NOT(ix.indnullsnotdistinct) AS nulls_distinct, + pg_get_expr(ix.indpred, ix.indrelid) AS where_clause, + am.amname AS using_method, + idx.indexdef + FROM + pg_index ix + JOIN + pg_class i ON ix.indexrelid = i.oid + JOIN + pg_class t ON ix.indrelid = t.oid + JOIN + pg_am am ON i.relam = am.oid + LEFT JOIN + pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p' + JOIN + pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary + JOIN information_schema.tables ta + ON ta.table_name = t.relname + WHERE + t.relname = $1 + AND ta.table_schema = $2 + AND c.conindid IS NULL + GROUP BY + i.relname, ix.indisunique, ix.indnullsnotdistinct, pg_get_expr(ix.indpred, ix.indrelid), am.amname, idx.indexdef; + """, + [spec.table_name, spec.schema], + log: false + ) + + %{ + spec + | indexes: + index_rows + |> Enum.flat_map(fn [ + index_name, + is_unique, + nulls_distinct, + where_clause, + using, + index_def + ] -> + index_name = String.slice(index_name, 0..63) + + case parse_columns_from_index_def(index_def, using) do + {:ok, columns} -> + include = + case String.split(index_def, "INCLUDE ") do + [_, included_cols] -> + try do + parse_columns(included_cols) + catch + :error -> + Logger.warning( + "Failed to parse includs from index definition: #{index_def}" + ) + + nil + end + + _ -> + nil + end + + [ + %Index{ + name: index_name, + columns: Enum.uniq(columns), + unique?: is_unique, + include: include, + using: using, + nulls_distinct: nulls_distinct, + where_clause: where_clause + } + ] + + :error -> + Logger.warning("Failed to parse index definition: #{index_def}") + [] + end + end) + } + end + + # CREATE INDEX users_lower_email_idx ON public.users USING btree (lower((email)::text)) + # CREATE INDEX unique_email_com3 ON public.users USING btree (email, id) WHERE (email ~~ '%.com'::citext) + defp parse_columns_from_index_def(string, using) do + string + |> String.trim_leading("CREATE ") + |> String.trim_leading("UNIQUE ") + |> String.trim_leading("INDEX ") + |> String.replace(~r/^[a-zA-Z0-9_\.]+\s/, "") + |> String.trim_leading("ON ") + |> String.replace(~r/^[\S]+/, "") + |> String.trim_leading() + |> String.trim_leading("USING #{using} ") + |> do_parse_columns() + |> then(&{:ok, &1}) + catch + :error -> :error + end + + def parse_columns(char) do + do_parse_columns(char) + end + + defp do_parse_columns(char, state \\ [], field \\ "", acc \\ []) + + defp do_parse_columns("(" <> rest, [], field, acc) do + do_parse_columns(rest, [:outer], field, acc) + end + + defp do_parse_columns(")" <> _rest, [:outer], field, acc) do + if field == "" do + Enum.reverse(acc) + else + Enum.reverse([field | acc]) + end + end + + defp do_parse_columns("(" <> rest, [:outer], field, acc) do + do_parse_columns(rest, [:in_paren, :in_field, :outer], field, acc) + end + + defp do_parse_columns(", " <> rest, [:in_field, :outer], field, acc) do + do_parse_columns(rest, [:in_field, :outer], "", [field | acc]) + end + + defp do_parse_columns(<> <> rest, [:outer], field, acc) do + do_parse_columns(rest, [:in_field, :outer], field <> str, acc) + end + + defp do_parse_columns("''" <> rest, [:in_quote | stack], field, acc) do + do_parse_columns(rest, [:in_quote | stack], field <> "'", acc) + end + + defp do_parse_columns("'" <> rest, [:in_quote | stack], field, acc) do + do_parse_columns(rest, stack, field <> "'", acc) + end + + defp do_parse_columns(<> <> rest, [:in_quote | stack], field, acc) do + do_parse_columns(rest, [:in_quote | stack], field <> str, acc) + end + + defp do_parse_columns("'" <> rest, stack, field, acc) do + do_parse_columns(rest, [:in_quote | stack], field <> "'", acc) + end + + defp do_parse_columns("(" <> rest, stack, field, acc) do + do_parse_columns(rest, [:in_paren | stack], field <> "(", acc) + end + + defp do_parse_columns(")" <> rest, [:in_paren | stack], field, acc) do + do_parse_columns(rest, stack, field <> ")", acc) + end + + defp do_parse_columns("), " <> rest, [:in_field | stack], field, acc) do + do_parse_columns(rest, [:in_field | stack], "", [field | acc]) + end + + defp do_parse_columns(")" <> _rest, [:in_field | _stack], field, acc) do + Enum.reverse([field | acc]) + end + + defp do_parse_columns(<> <> rest, [:in_paren | stack], field, acc) do + do_parse_columns(rest, [:in_paren | stack], field <> str, acc) + end + + defp do_parse_columns(<> <> rest, [:outer], field, acc) do + do_parse_columns(rest, [:in_field, :outer], field <> str, acc) + end + + defp do_parse_columns(<> <> rest, [:in_field | stack], field, acc) do + do_parse_columns(rest, [:in_field | stack], field <> str, acc) + end + + defp do_parse_columns(", " <> rest, [:in_field | stack], field, acc) do + do_parse_columns(rest, stack, "", [field | acc]) + end + + defp do_parse_columns(")" <> _rest, [:outer], field, acc) do + Enum.reverse([field | acc]) + end + + defp do_parse_columns("", [:in_field | _stack], field, acc) do + Enum.reverse([field | acc]) + end + + defp do_parse_columns("", [:outer], field, acc) do + if field == "" do + Enum.reverse(acc) + else + Enum.reverse([field | acc]) + end + end + + defp do_parse_columns(other, stack, field, acc) do + raise "Unexpected character: #{inspect(other)} at #{inspect(stack)} with #{inspect(field)} - #{inspect(acc)}" + end + + defp verify_found_tables(specs, opts) do + if opts[:tables] do + not_found = + Enum.reject(opts[:tables], fn table -> + if String.ends_with?(table, ".") do + true + else + case String.split(table, ".") do + [schema, table] -> + Enum.any?(specs, fn spec -> + Enum.at(spec, 0) == table and Enum.at(spec, 1) == schema + end) + + [table] -> + Enum.any?(specs, fn spec -> + Enum.at(spec, 0) == table + end) + end + end + end) + + case not_found do + [] -> + specs + + tables -> + raise "The following tables did not exist: #{inspect(tables)}" + end + else + specs + end + end + + defp build_attributes(attributes, table_name, schema, repo) do + attributes + |> set_primary_key(table_name, schema, repo) + |> set_sensitive() + |> set_types() + |> set_defaults_and_generated() + end + + defp set_defaults_and_generated(attributes) do + Enum.map(attributes, fn attribute -> + attribute = + if attribute.migration_default do + %{attribute | generated?: true} + else + attribute + end + + case attribute do + %{migration_default: nil} -> + attribute + + %{migration_default: "CURRENT_TIMESTAMP"} -> + %{attribute | default: &DateTime.utc_now/0} + + %{migration_default: "now()"} -> + %{attribute | default: &DateTime.utc_now/0} + + %{migration_default: "(now() AT TIME ZONE 'utc'::text)"} -> + %{attribute | default: &DateTime.utc_now/0} + + %{migration_default: "gen_random_uuid()"} -> + %{attribute | default: &Ash.UUID.generate/0} + + %{migration_default: "uuid_generate_v4()"} -> + %{attribute | default: &Ash.UUID.generate/0} + + %{attr_type: :integer, migration_default: value} -> + case Integer.parse(value) do + {value, ""} -> + %{attribute | default: value} + + _ -> + attribute + end + + %{attr_type: :decimal, migration_default: value} -> + case Decimal.parse(value) do + {value, ""} -> + %{attribute | default: Decimal.new(value)} + + _ -> + attribute + end + + %{attr_type: :map, migration_default: value} -> + case Jason.decode(String.trim_trailing(value, "::json")) do + {:ok, value} -> + %{attribute | default: value} + + _ -> + attribute + end + + %{attr_type: type, migration_default: "'" <> value} + when type in [:string, :ci_string, :atom] -> + case String.trim_trailing(value, "'::text") do + ^value -> + attribute + + trimmed -> + # This is very likely too naive + attribute = %{attribute | default: String.replace(trimmed, "''", "'")} + + if type == :atom do + %{attribute | default: String.to_atom(attribute.default)} + else + attribute + end + end + + _ -> + attribute + end + end) + end + + def add_relationships(specs, resources) do + specs + |> Enum.group_by(& &1.repo) + |> Enum.flat_map(fn {repo, specs} -> + do_add_relationships( + specs, + Enum.flat_map(resources, fn {resource, resource_repo, table} -> + if resource_repo == repo do + [{resource, table}] + else + [] + end + end) + ) + end) + end + + defp do_add_relationships(specs, resources) do + specs = + Enum.map(specs, fn spec -> + belongs_to_relationships = + Enum.flat_map( + spec.foreign_keys, + fn %ForeignKey{ + constraint_name: constraint_name, + column: column_name, + references: references, + destination_field: destination_field, + match_with: match_with + } -> + case find_destination_and_field( + specs, + spec, + references, + destination_field, + resources, + match_with + ) do + nil -> + [] + + {destination, destination_attribute, match_with} -> + source_attr = + Enum.find(spec.attributes, fn attribute -> + attribute.source == column_name + end) + + [ + %Relationship{ + type: :belongs_to, + name: Inflex.singularize(references), + source: spec.resource, + constraint_name: constraint_name, + match_with: match_with, + destination: destination, + source_attribute: source_attr.name, + allow_nil?: source_attr.allow_nil?, + destination_attribute: destination_attribute + } + ] + end + end + ) + |> Enum.group_by(& &1.name) + |> Enum.flat_map(fn + {_name, [relationship]} -> + [relationship] + + {name, relationships} -> + name_all_relationships(:belongs_to, spec, name, relationships) + end) + + %{spec | relationships: belongs_to_relationships} + end) + + Enum.map(specs, fn spec -> + relationships_to_me = + Enum.flat_map(specs, fn other_spec -> + Enum.flat_map(other_spec.relationships, fn relationship -> + if relationship.destination == spec.resource do + [{other_spec.table_name, other_spec.resource, relationship}] + else + [] + end + end) + end) + |> Enum.map(fn {table, resource, relationship} -> + destination_field = + Enum.find(spec.attributes, fn attribute -> + attribute.name == relationship.destination_attribute + end).source + + has_unique_index? = + Enum.any?(spec.indexes, fn index -> + index.unique? and is_nil(index.where_clause) and + index.columns == [destination_field] + end) + + {name, type} = + if has_unique_index? do + if Inflex.pluralize(table) == table do + {Inflex.singularize(table), :has_one} + else + {table, :has_one} + end + else + if Inflex.pluralize(table) == table do + {table, :has_many} + else + {Inflex.pluralize(table), :has_many} + end + end + + %Relationship{ + type: type, + name: name, + destination: resource, + source: spec.resource, + match_with: + Enum.map(relationship.match_with, fn {source, dest} -> + {dest, source} + end), + constraint_name: relationship.constraint_name, + source_attribute: relationship.destination_attribute, + destination_attribute: relationship.source_attribute + } + end) + |> Enum.group_by(& &1.name) + |> Enum.flat_map(fn + {_name, [relationship]} -> + [relationship] + + {name, relationships} -> + name_all_relationships(:has, spec, name, relationships) + end) + + %{spec | relationships: spec.relationships ++ relationships_to_me} + end) + end + + defp name_all_relationships(type, spec, name, relationships, acc \\ []) + defp name_all_relationships(_type, _spec, _name, [], acc), do: acc + + defp name_all_relationships(type, spec, name, [relationship | rest], acc) do + label = + case type do + :belongs_to -> + """ + Multiple foreign keys found from `#{inspect(spec.resource)}` to `#{inspect(relationship.destination)}` with the guessed name `#{name}`. + + Provide a relationship name for the one with the following info: + + Resource: `#{inspect(spec.resource)}` + Relationship Type: :belongs_to + Guessed Name: `:#{name}` + Relationship Destination: `#{inspect(relationship.destination)}` + Constraint Name: `#{inspect(relationship.constraint_name)}`. + + Leave empty to skip adding this relationship. + + Name: + """ + |> String.trim_trailing() + + _ -> + """ + Multiple foreign keys found from `#{inspect(relationship.source)}` to `#{inspect(spec.resource)}` with the guessed name `#{name}`. + + Provide a relationship name for the one with the following info: + + Resource: `#{inspect(relationship.source)}` + Relationship Type: :#{relationship.type} + Guessed Name: `:#{name}` + Relationship Destination: `#{inspect(spec.resource)}` + Constraint Name: `#{inspect(relationship.constraint_name)}`. + + Leave empty to skip adding this relationship. + + Name: + """ + |> String.trim_trailing() + end + + Owl.IO.input(label: label) + |> String.trim() + # common typo + |> String.trim_leading(":") + |> case do + "" -> + name_all_relationships(type, spec, name, rest, acc) + + new_name -> + name_all_relationships(type, spec, name, rest, [%{relationship | name: new_name} | acc]) + end + end + + defp find_destination_and_field( + specs, + spec, + destination, + destination_field, + resources, + match_with + ) do + case Enum.find(specs, fn other_spec -> + other_spec.table_name == destination + end) do + nil -> + case Enum.find(resources, fn {_resource, table} -> + table == destination + end) do + nil -> + nil + + {resource, _table} -> + # this is cheating. We should be making sure the app is compiled + # before our task is composed or pulling from source code + attributes = + resource + |> Ash.Resource.Info.attributes() + + case Enum.reduce_while(match_with, {:ok, []}, fn {source, dest}, {:ok, acc} -> + with source_attr when not is_nil(source_attr) <- + Enum.find(spec.attributes, &(&1.source == source)), + dest_attr when not is_nil(dest_attr) <- + Enum.find(attributes, &(to_string(&1.source) == dest)) do + {:cont, {:ok, acc ++ [{source_attr.name, to_string(dest_attr.name)}]}} + else + _ -> + {:halt, :error} + end + end) do + {:ok, match_with} -> + Enum.find_value(attributes, fn attribute -> + if to_string(attribute.source) == destination_field do + {resource, to_string(attribute.name), match_with} + end + end) + + _ -> + nil + end + end + + %__MODULE__{} = other_spec -> + case Enum.reduce_while(match_with, {:ok, []}, fn {source, dest}, {:ok, acc} -> + with source_attr when not is_nil(source_attr) <- + Enum.find(spec.attributes, &(&1.source == source)), + dest_attr when not is_nil(dest_attr) <- + Enum.find(other_spec.attributes, &(&1.source == dest)) do + {:cont, {:ok, acc ++ [{source_attr.name, dest_attr.name}]}} + else + _ -> + {:halt, :error} + end + end) do + {:ok, match_with} -> + other_spec.attributes + |> Enum.find_value(fn %Attribute{} = attr -> + if attr.source == destination_field do + {other_spec.resource, attr.name, match_with} + end + end) + + _ -> + nil + end + end + end + + def set_types(attributes) do + attributes + |> Enum.map(fn attribute -> + case Process.get({:type_cache, attribute.type}) do + nil -> + case type(attribute.type) do + {:ok, type} -> + %{attribute | attr_type: type} + + :error -> + get_type(attribute) + end + + type -> + %{attribute | attr_type: type} + end + end) + end + + defp get_type(attribute) do + case Mix.shell().prompt(""" + Unknown type: #{attribute.type}. What should we use as the type? + + Provide the value as literal source code that should be placed into the + generated file, i.e + + - :string + - MyApp.Types.CustomType + - {:array, :string} + + Use `skip` to skip ignore this attribute. + """) do + skip when skip in ["skip", "skip\n"] -> + attribute + + new_type -> + case String.trim(new_type) do + ":" <> type -> + new_type = String.to_atom(type) + Process.put({:type_cache, attribute.type}, new_type) + %{attribute | attr_type: new_type} + + type -> + try do + Code.eval_string(type) + Process.put({:type_cache, attribute.type}, new_type) + %{attribute | attr_type: new_type} + rescue + e -> + IO.puts(Exception.format(:error, e, __STACKTRACE__)) + + get_type(attribute) + end + end + end + end + + defp type("ARRAY of " <> rest) do + case type(rest) do + {:ok, type} -> {:ok, {:array, type}} + :error -> :error + end + end + + defp type("bigint"), do: {:ok, :integer} + defp type("bigserial"), do: {:ok, :integer} + defp type("boolean"), do: {:ok, :boolean} + defp type("bytea"), do: {:ok, :binary} + defp type("varchar"), do: {:ok, :string} + defp type("character varying"), do: {:ok, :string} + defp type("date"), do: {:ok, :date} + defp type("double precision"), do: {:ok, :decimal} + defp type("int"), do: {:ok, :integer} + defp type("integer"), do: {:ok, :integer} + defp type("json"), do: {:ok, :map} + defp type("jsonb"), do: {:ok, :map} + defp type("numeric"), do: {:ok, :decimal} + defp type("decimal"), do: {:ok, :decimal} + defp type("smallint"), do: {:ok, :integer} + defp type("smallserial"), do: {:ok, :ineger} + defp type("serial"), do: {:ok, :integer} + defp type("text"), do: {:ok, :string} + defp type("time"), do: {:ok, :time} + defp type("time without time zone"), do: {:ok, :time} + defp type("time with time zone"), do: {:ok, :time} + defp type("timestamp"), do: {:ok, :utc_datetime_usec} + defp type("timestamp without time zone"), do: {:ok, :utc_datetime_usec} + defp type("timestamp with time zone"), do: {:ok, :utc_datetime_usec} + defp type("tsquery"), do: {:ok, AshPostgres.Tsquery} + defp type("tsvector"), do: {:ok, AshPostgres.Tsvector} + defp type("uuid"), do: {:ok, :uuid} + defp type("citext"), do: {:ok, :ci_string} + defp type(_), do: :error + + defp set_sensitive(attributes) do + Enum.map(attributes, fn attribute -> + %{ + attribute + | sensitive?: AshPostgres.ResourceGenerator.SensitiveData.is_sensitive?(attribute.name) + } + end) + end + + defp set_primary_key(attributes, table_name, schema, repo) do + %Postgrex.Result{rows: pkey_rows} = + repo.query!( + """ + SELECT c.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name) + JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema + AND tc.table_name = c.table_name AND ccu.column_name = c.column_name + WHERE constraint_type = 'PRIMARY KEY' and tc.table_name = $1 AND tc.table_schema = $2; + """, + [table_name, schema], + log: false + ) + + Enum.map(attributes, fn %Attribute{name: name} = attribute -> + %{attribute | primary_key?: [name] in pkey_rows} + end) + end + + defp table_specs(repo, opts) do + Enum.flat_map(opts[:tables] || ["public."], fn table -> + {schema, table} = + case String.split(table, ".") do + [schema, table] -> + {schema, table} + + [table] -> + {"public", table} + end + + %{rows: rows} = + if table == "" do + repo.query!( + """ + SELECT + t.table_name, + t.table_schema, + c.column_name, + CASE WHEN c.data_type = 'ARRAY' THEN + repeat('ARRAY of ', a.attndims) || REPLACE(c.udt_name, '_', '') + WHEN c.data_type = 'USER-DEFINED' THEN + c.udt_name + ELSE + c.data_type + END as data_type, + c.column_default, + c.character_maximum_length, + c.is_nullable = 'YES' + FROM + information_schema.tables t + JOIN + information_schema.columns c + ON t.table_name = c.table_name + JOIN pg_attribute a + ON a.attrelid = (SELECT oid FROM pg_class WHERE relname = t.table_name AND relkind = 'r') + AND a.attname = c.column_name + AND a.attnum > 0 + WHERE + t.table_name NOT LIKE 'pg_%' + AND t.table_name NOT LIKE '_pg_%' + AND t.table_schema = $1 + ORDER BY + t.table_name, + c.ordinal_position; + """, + [schema], + log: false + ) + else + repo.query!( + """ + SELECT + t.table_name, + t.table_schema, + c.column_name, + CASE WHEN c.data_type = 'ARRAY' THEN + repeat('ARRAY of ', a.attndims) || REPLACE(c.udt_name, '_', '') + WHEN c.data_type = 'USER-DEFINED' THEN + c.udt_name + ELSE + c.data_type + END as data_type, + c.column_default, + c.character_maximum_length, + c.is_nullable = 'YES' + FROM + information_schema.tables t + JOIN + information_schema.columns c + ON t.table_name = c.table_name + JOIN pg_attribute a + ON a.attrelid = (SELECT oid FROM pg_class WHERE relname = t.table_name AND relkind = 'r') + AND a.attname = c.column_name + AND a.attnum > 0 + WHERE + t.table_schema = $1 + AND t.table_name = $2 + ORDER BY + t.table_name, + c.ordinal_position; + """, + [schema, table], + log: false + ) + end + + rows + end) + end +end diff --git a/mix.exs b/mix.exs index 63d080c4..d4b8e368 100644 --- a/mix.exs +++ b/mix.exs @@ -169,6 +169,8 @@ defmodule AshPostgres.MixProject do {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, + {:inflex, "~> 2.1"}, + {:owl, "~> 0.11"}, # dev/test dependencies {:eflame, "~> 1.0", only: [:dev, :test]}, {:simple_sat, "~> 0.1", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index ea609ef7..6410f216 100644 --- a/mix.lock +++ b/mix.lock @@ -8,15 +8,15 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "ecto": {:hex, :ecto, "3.12.2", "bae2094f038e9664ce5f089e5f3b6132a535d8b018bd280a485c2f33df5c0ce1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e67c70f3a71c6afe80d946d3ced52ecc57c53c9829791bfff1830ff5a1f0c"}, "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "a663c13478a49d29ae0267b6e45badb803267cf0", []}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "d571628fd829a510d219bcb7162400baff50977f", []}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, @@ -25,9 +25,9 @@ "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, diff --git a/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json b/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json new file mode 100644 index 00000000..06016f77 --- /dev/null +++ b/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json @@ -0,0 +1,29 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "F24673A4219DEC6873571CCF68B8F0CC34B5843DAA2D7B71A16EFE576C385C1C", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "schematic_groups" +} \ No newline at end of file diff --git a/test/support/domain.ex b/test/support/domain.ex index a1037a47..5fefbd37 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -21,6 +21,15 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) resource(AshPostgres.Test.StatefulPostFollower) + resource(CalcDependency.Dependency) + resource(CalcDependency.Element) + resource(CalcDependency.ElementContext) + resource(CalcDependency.Location) + resource(CalcDependency.Operation) + resource(CalcDependency.OperationVersion) + resource(CalcDependency.SchematicGroup) + resource(CalcDependency.Segment) + resource(CalcDependency.Verb) end authorization do From efa4ef03f14de04b5747ed16e2da85599b669ca3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Sep 2024 20:28:45 -0400 Subject: [PATCH 0675/1215] feat: `mix ash_postgres.gen.resources` --- lib/mix/tasks/ash_postgres.gen.resources.ex | 5 +- lib/resource_generator/resource_generator.ex | 2 +- lib/resource_generator/spec.ex | 62 ++++++++++++-------- test/resource_generator_test.exs | 27 +++++++++ 4 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 test/resource_generator_test.exs diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 4bb2cd05..73605422 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -23,6 +23,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. + - `yes`, `y` - Answer yes (or skip) to all questions. ## Tables @@ -41,6 +42,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do example: @example, schema: [ repo: :keep, + yes: :boolean, tables: :keep, skip_tables: :keep, snapshots_only: :boolean, @@ -48,6 +50,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do ], aliases: [ t: :tables, + y: :boolean, r: :repo, d: :domain, s: :skip_tables @@ -109,7 +112,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do """ options = - if Mix.shell().yes?(prompt) do + if options[:yes] || Mix.shell().yes?(prompt) do Keyword.put(options, :no_migrations, false) else Keyword.put(options, :no_migrations, true) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 9ba9586f..2b4b395c 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -84,7 +84,7 @@ defmodule AshPostgres.ResourceGenerator do with `--tables` or `--skip-tables` """ end) - |> Spec.add_relationships(resources) + |> Spec.add_relationships(resources, opts) Enum.reduce(specs, igniter, fn table_spec, igniter -> table_to_resource(igniter, table_spec, domain, opts) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 1b73fc41..4a20c1bc 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -85,7 +85,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do } end) |> Enum.map(fn {[table_name, table_schema], attributes} -> - attributes = build_attributes(attributes, table_name, table_schema, repo) + attributes = build_attributes(attributes, table_name, table_schema, repo, opts) %__MODULE__{ table_name: table_name, @@ -454,11 +454,11 @@ defmodule AshPostgres.ResourceGenerator.Spec do end end - defp build_attributes(attributes, table_name, schema, repo) do + defp build_attributes(attributes, table_name, schema, repo, opts) do attributes |> set_primary_key(table_name, schema, repo) |> set_sensitive() - |> set_types() + |> set_types(opts) |> set_defaults_and_generated() end @@ -540,7 +540,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do end) end - def add_relationships(specs, resources) do + def add_relationships(specs, resources, opts) do specs |> Enum.group_by(& &1.repo) |> Enum.flat_map(fn {repo, specs} -> @@ -552,12 +552,13 @@ defmodule AshPostgres.ResourceGenerator.Spec do else [] end - end) + end), + opts ) end) end - defp do_add_relationships(specs, resources) do + defp do_add_relationships(specs, resources, opts) do specs = Enum.map(specs, fn spec -> belongs_to_relationships = @@ -609,7 +610,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do [relationship] {name, relationships} -> - name_all_relationships(:belongs_to, spec, name, relationships) + name_all_relationships(:belongs_to, opts, spec, name, relationships) end) %{spec | relationships: belongs_to_relationships} @@ -673,17 +674,17 @@ defmodule AshPostgres.ResourceGenerator.Spec do [relationship] {name, relationships} -> - name_all_relationships(:has, spec, name, relationships) + name_all_relationships(:has, opts, spec, name, relationships) end) %{spec | relationships: spec.relationships ++ relationships_to_me} end) end - defp name_all_relationships(type, spec, name, relationships, acc \\ []) - defp name_all_relationships(_type, _spec, _name, [], acc), do: acc + defp name_all_relationships(type, opts, spec, name, relationships, acc \\ []) + defp name_all_relationships(_type, _opts, _spec, _name, [], acc), do: acc - defp name_all_relationships(type, spec, name, [relationship | rest], acc) do + defp name_all_relationships(type, opts, spec, name, [relationship | rest], acc) do label = case type do :belongs_to -> @@ -729,10 +730,12 @@ defmodule AshPostgres.ResourceGenerator.Spec do |> String.trim_leading(":") |> case do "" -> - name_all_relationships(type, spec, name, rest, acc) + name_all_relationships(type, opts, spec, name, rest, acc) new_name -> - name_all_relationships(type, spec, name, rest, [%{relationship | name: new_name} | acc]) + name_all_relationships(type, opts, spec, name, rest, [ + %{relationship | name: new_name} | acc + ]) end end @@ -810,7 +813,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do end end - def set_types(attributes) do + def set_types(attributes, opts) do attributes |> Enum.map(fn attribute -> case Process.get({:type_cache, attribute.type}) do @@ -820,7 +823,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do %{attribute | attr_type: type} :error -> - get_type(attribute) + get_type(attribute, opts) end type -> @@ -829,19 +832,26 @@ defmodule AshPostgres.ResourceGenerator.Spec do end) end - defp get_type(attribute) do - case Mix.shell().prompt(""" - Unknown type: #{attribute.type}. What should we use as the type? + defp get_type(attribute, opts) do + result = + if opts[:yes?] do + "skip" + else + Mix.shell().prompt(""" + Unknown type: #{attribute.type}. What should we use as the type? + + Provide the value as literal source code that should be placed into the + generated file, i.e - Provide the value as literal source code that should be placed into the - generated file, i.e + - :string + - MyApp.Types.CustomType + - {:array, :string} - - :string - - MyApp.Types.CustomType - - {:array, :string} + Use `skip` to skip ignore this attribute. + """) + end - Use `skip` to skip ignore this attribute. - """) do + case result do skip when skip in ["skip", "skip\n"] -> attribute @@ -861,7 +871,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do e -> IO.puts(Exception.format(:error, e, __STACKTRACE__)) - get_type(attribute) + get_type(attribute, opts) end end end diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs new file mode 100644 index 00000000..f501d26b --- /dev/null +++ b/test/resource_generator_test.exs @@ -0,0 +1,27 @@ +defmodule AshPostgres.RelWithParentFilterTest do + use AshPostgres.RepoCase, async: false + + setup do + AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS example_table") + AshPostgres.TestRepo.query!("CREATE TABLE example_table ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + name VARCHAR(255), + age INTEGER, + email VARCHAR(255) + )") + + on_exit(fn -> + AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS example_table") + end) + end + + test "a resource is generated from a table" do + Igniter.new() + |> Igniter.compose_task("ash_postgres.gen.resources", [ + "MyApp.Accounts", + "--tables", + "example_table", + "--yes" + ]) + end +end From 3fece442301a0c70f2a9d3d28198a55fbdaf5775 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Sep 2024 22:58:36 -0400 Subject: [PATCH 0676/1215] chore: add the new guide to the docs in mix.exs --- mix.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/mix.exs b/mix.exs index d4b8e368..700bfabf 100644 --- a/mix.exs +++ b/mix.exs @@ -87,6 +87,7 @@ defmodule AshPostgres.MixProject do extras: [ {"README.md", title: "Home"}, "documentation/tutorials/get-started-with-ash-postgres.md", + "documentation/tutorials/set-up-with-existing-database.md", "documentation/topics/about-ash-postgres/what-is-ash-postgres.md", "documentation/topics/resources/references.md", "documentation/topics/resources/polymorphic-resources.md", From f80b157d067b70b77d681d81e2cbcbbdd7636a16 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Sep 2024 23:02:03 -0400 Subject: [PATCH 0677/1215] chore: remove testing modules not in use --- test/support/domain.ex | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/support/domain.ex b/test/support/domain.ex index 5fefbd37..a1037a47 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -21,15 +21,6 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) resource(AshPostgres.Test.StatefulPostFollower) - resource(CalcDependency.Dependency) - resource(CalcDependency.Element) - resource(CalcDependency.ElementContext) - resource(CalcDependency.Location) - resource(CalcDependency.Operation) - resource(CalcDependency.OperationVersion) - resource(CalcDependency.SchematicGroup) - resource(CalcDependency.Segment) - resource(CalcDependency.Verb) end authorization do From 9191bd32696216508314c8c451c4fda57e1fa536 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 00:31:38 -0400 Subject: [PATCH 0678/1215] chore: perf fixes, build, and test --- lib/igniter.ex | 8 ++-- lib/mix/tasks/ash_postgres.gen.resources.ex | 2 +- lib/resource_generator/resource_generator.ex | 47 +++++-------------- lib/resource_generator/sensitive_data.ex | 3 +- lib/resource_generator/spec.ex | 48 ++++---------------- test/resource_generator_test.exs | 48 ++++++++++++++++---- test/support/resources/post.ex | 1 + 7 files changed, 68 insertions(+), 89 deletions(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 662418e0..63fe886d 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -25,8 +25,8 @@ defmodule AshPostgres.Igniter do {igniter, {:ok, value}} when is_binary(value) or is_nil(value) -> {:ok, igniter, value} - _ -> - :error + {igniter, _} -> + {:error, igniter} end end @@ -37,8 +37,8 @@ defmodule AshPostgres.Igniter do {igniter, {:ok, value}} when is_atom(value) -> {:ok, igniter, value} - _ -> - :error + {igniter, _} -> + {:error, igniter} end end diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 73605422..346bbdac 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -5,7 +5,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do @shortdoc "Generates or updates resources based on a database schema" - @doc """ + @moduledoc """ #{@shortdoc} ## Example diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 2b4b395c..81ef6afb 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -1,4 +1,5 @@ defmodule AshPostgres.ResourceGenerator do + @moduledoc false alias AshPostgres.ResourceGenerator.Spec require Logger @@ -6,26 +7,9 @@ defmodule AshPostgres.ResourceGenerator do def generate(igniter, repos, domain, opts \\ []) do {igniter, resources} = Ash.Resource.Igniter.list_resources(igniter) - resources = - Task.async_stream(resources, fn resource -> - {resource, AshPostgres.Igniter.repo(igniter, resource), - AshPostgres.Igniter.table(igniter, resource)} - end) - |> Enum.map(fn {:ok, {resource, repo, table}} -> - repo = - case repo do - {:ok, _igniter, repo} -> repo - _ -> nil - end - - table = - case table do - {:ok, _igniter, table} -> table - _ -> nil - end - - {resource, repo, table} - end) + # This is a hack. We should be looking at compiled resources + # unlikely to ever matter given how this task will be used though. + resources = Enum.filter(resources, &Code.ensure_loaded?/1) igniter = Igniter.include_all_elixir_files(igniter) @@ -140,8 +124,6 @@ defmodule AshPostgres.ResourceGenerator do end defp check_constraints(%{check_constraints: check_constraints}, _) do - IO.inspect(check_constraints) - check_constraints = Enum.map_join(check_constraints, "\n", fn check_constraint -> """ @@ -158,9 +140,8 @@ defmodule AshPostgres.ResourceGenerator do defp skip_unique_indexes(%{indexes: indexes}) do indexes - |> Enum.filter(& &1.unique?) - |> Enum.filter(fn %{columns: columns} -> - Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + |> Enum.filter(fn %{unique?: unique?, columns: columns} -> + unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) end) |> Enum.reject(&index_as_identity?/1) |> case do @@ -176,9 +157,8 @@ defmodule AshPostgres.ResourceGenerator do defp identity_index_names(%{indexes: indexes}) do indexes - |> Enum.filter(& &1.unique?) - |> Enum.filter(fn %{columns: columns} -> - Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + |> Enum.filter(fn %{unique?: unique?, columns: columns} -> + unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) end) |> case do [] -> @@ -195,9 +175,8 @@ defmodule AshPostgres.ResourceGenerator do defp add_identities(str, %{indexes: indexes}) do indexes - |> Enum.filter(& &1.unique?) - |> Enum.filter(fn %{columns: columns} -> - Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + |> Enum.filter(fn %{unique?: unique?, columns: columns} -> + unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) end) |> Enum.map(fn index -> name = index.name @@ -431,10 +410,8 @@ defmodule AshPostgres.ResourceGenerator do defp custom_indexes(table_spec, true) do table_spec.indexes |> Enum.reject(fn index -> - !index.unique? || (&index_as_identity?/1) - end) - |> Enum.reject(fn index -> - Enum.any?(index.columns, &String.contains?(&1, "(")) + !index.unique? || (&index_as_identity?/1) || + Enum.any?(index.columns, &String.contains?(&1, "(")) end) |> case do [] -> diff --git a/lib/resource_generator/sensitive_data.ex b/lib/resource_generator/sensitive_data.ex index 10fa3691..77b0e45b 100644 --- a/lib/resource_generator/sensitive_data.ex +++ b/lib/resource_generator/sensitive_data.ex @@ -1,4 +1,5 @@ defmodule AshPostgres.ResourceGenerator.SensitiveData do + @moduledoc false # I got this from ChatGPT, but this is a best effort transformation # anyway. @sensitive_patterns [ @@ -65,7 +66,7 @@ defmodule AshPostgres.ResourceGenerator.SensitiveData do ~r/.*_token/i ] - def is_sensitive?(column_name) do + def sensitive?(column_name) do Enum.any?(@sensitive_patterns, fn pattern -> Regex.match?(pattern, column_name) end) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 4a20c1bc..fe9fa24c 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -1,4 +1,5 @@ defmodule AshPostgres.ResourceGenerator.Spec do + @moduledoc false require Logger defstruct [ @@ -15,6 +16,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do ] defmodule Attribute do + @moduledoc false defstruct [ :name, :type, @@ -31,6 +33,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do end defmodule ForeignKey do + @moduledoc false defstruct [ :constraint_name, :match_type, @@ -44,14 +47,17 @@ defmodule AshPostgres.ResourceGenerator.Spec do end defmodule Index do + @moduledoc false defstruct [:name, :columns, :unique?, :nulls_distinct, :where_clause, :using, :include] end defmodule CheckConstraint do + @moduledoc false defstruct [:name, :column, :expression] end defmodule Relationship do + @moduledoc false defstruct [ :name, :type, @@ -71,7 +77,6 @@ defmodule AshPostgres.ResourceGenerator.Spec do Ecto.Migrator.with_repo(repo, fn repo -> repo |> table_specs(opts) - |> verify_found_tables(opts) |> Enum.group_by(&Enum.take(&1, 2), fn [_, _, field, type, default, size, allow_nil?] -> name = Macro.underscore(field) @@ -421,39 +426,6 @@ defmodule AshPostgres.ResourceGenerator.Spec do raise "Unexpected character: #{inspect(other)} at #{inspect(stack)} with #{inspect(field)} - #{inspect(acc)}" end - defp verify_found_tables(specs, opts) do - if opts[:tables] do - not_found = - Enum.reject(opts[:tables], fn table -> - if String.ends_with?(table, ".") do - true - else - case String.split(table, ".") do - [schema, table] -> - Enum.any?(specs, fn spec -> - Enum.at(spec, 0) == table and Enum.at(spec, 1) == schema - end) - - [table] -> - Enum.any?(specs, fn spec -> - Enum.at(spec, 0) == table - end) - end - end - end) - - case not_found do - [] -> - specs - - tables -> - raise "The following tables did not exist: #{inspect(tables)}" - end - else - specs - end - end - defp build_attributes(attributes, table_name, schema, repo, opts) do attributes |> set_primary_key(table_name, schema, repo) @@ -546,9 +518,9 @@ defmodule AshPostgres.ResourceGenerator.Spec do |> Enum.flat_map(fn {repo, specs} -> do_add_relationships( specs, - Enum.flat_map(resources, fn {resource, resource_repo, table} -> - if resource_repo == repo do - [{resource, table}] + Enum.flat_map(resources, fn resource -> + if AshPostgres.DataLayer.Info.repo(resource) == repo do + [{resource, AshPostgres.DataLayer.Info.table(resource)}] else [] end @@ -918,7 +890,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do Enum.map(attributes, fn attribute -> %{ attribute - | sensitive?: AshPostgres.ResourceGenerator.SensitiveData.is_sensitive?(attribute.name) + | sensitive?: AshPostgres.ResourceGenerator.SensitiveData.sensitive?(attribute.name) } end) end diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index f501d26b..50bfa7db 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -3,6 +3,7 @@ defmodule AshPostgres.RelWithParentFilterTest do setup do AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS example_table") + AshPostgres.TestRepo.query!("CREATE TABLE example_table ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, name VARCHAR(255), @@ -10,18 +11,45 @@ defmodule AshPostgres.RelWithParentFilterTest do email VARCHAR(255) )") - on_exit(fn -> - AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS example_table") - end) + :ok end test "a resource is generated from a table" do - Igniter.new() - |> Igniter.compose_task("ash_postgres.gen.resources", [ - "MyApp.Accounts", - "--tables", - "example_table", - "--yes" - ]) + resource = + Igniter.new() + |> Igniter.compose_task("ash_postgres.gen.resources", [ + "MyApp.Accounts", + "--tables", + "example_table", + "--yes" + ]) + |> Igniter.prepare_for_write() + |> Map.get(:rewrite) + |> Rewrite.source!("lib/my_app/accounts/example_table.ex") + |> Rewrite.Source.get(:content) + + assert String.trim(resource) == + String.trim(""" + defmodule MyApp.Accounts.ExampleTable do + use Ash.Resource, + domain: MyApp.Accounts, + data_layer: AshPostgres.DataLayer + + postgres do + table "example_table" + repo AshPostgres.TestRepo + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string) + attribute(:age, :integer) + + attribute :email, :string do + sensitive?(true) + end + end + end + """) end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e4f64c23..fabbdc6d 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -50,6 +50,7 @@ defmodule HasNoComments do end defmodule CiCategory do + @moduledoc false use Ash.Type.NewType, subtype_of: :ci_string end From 5395aba898c3e01eb51b59d654918775040324df Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 01:21:09 -0400 Subject: [PATCH 0679/1215] chore: release version v2.3.0 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be827fc1..ae1e8191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.3.0](https://github.com/ash-project/ash_postgres/compare/v2.2.5...v2.3.0) (2024-09-05) + + + + +### Features: + +* `mix ash_postgres.gen.resources` + +* `mix ash_postgres.gen.resources` + ## [v2.2.5](https://github.com/ash-project/ash_postgres/compare/v2.2.4...v2.2.5) (2024-09-04) ### Improvements: diff --git a/mix.exs b/mix.exs index 700bfabf..bc665a66 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.2.5" + @version "2.3.0" def project do [ From a9913a699999e7f98a5472c33921f223a8743831 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 12:54:13 -0400 Subject: [PATCH 0680/1215] improvement: better imported index names improvement: add `--extend` option, forwarded to generated resource --- lib/mix/tasks/ash_postgres.gen.resources.ex | 3 + lib/resource_generator/resource_generator.ex | 66 ++++++++++---------- lib/resource_generator/spec.ex | 23 ++++++- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 346bbdac..5e270f8e 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -23,6 +23,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. + - `extend`, `e` - Extension or extensions to apply to the generated resources. See `mix ash.patch.extend` for more. - `yes`, `y` - Answer yes (or skip) to all questions. ## Tables @@ -45,6 +46,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do yes: :boolean, tables: :keep, skip_tables: :keep, + extend: :keep, snapshots_only: :boolean, domain: :keep ], @@ -52,6 +54,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do t: :tables, y: :boolean, r: :repo, + e: :extend, d: :domain, s: :skip_tables ] diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 81ef6afb..d29b5800 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -13,33 +13,7 @@ defmodule AshPostgres.ResourceGenerator do igniter = Igniter.include_all_elixir_files(igniter) - opts = - if opts[:tables] do - Keyword.put( - opts, - :tables, - opts[:tables] - |> List.wrap() - |> Enum.join(",") - |> String.split(",") - ) - else - opts - end - - opts = - if opts[:skip_tables] do - Keyword.put( - opts, - :skip_tables, - opts[:skip_tables] - |> List.wrap() - |> Enum.join(",") - |> String.split(",") - ) - else - opts - end + opts = handle_csv_opts(opts, [:tables, :skip_tables, :extend]) specs = repos @@ -75,6 +49,23 @@ defmodule AshPostgres.ResourceGenerator do end) end + defp handle_csv_opts(opts, keys) do + Enum.reduce(keys, opts, fn key, opts -> + opts + |> Keyword.get_values(key) + |> case do + [] -> + opts + + values -> + values + |> Enum.join(",") + |> String.split(",", trim: true) + |> then(&Keyword.put(opts, key, &1)) + end + end) + end + defp table_to_resource( igniter, %AshPostgres.ResourceGenerator.Spec{} = table_spec, @@ -113,6 +104,15 @@ defmodule AshPostgres.ResourceGenerator do igniter |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) |> Igniter.Code.Module.create_module(table_spec.resource, resource) + |> then(fn igniter -> + if opts[:extend] do + Igniter.compose_task(igniter, "ash.patch.extend", [ + table_spec.resource | opts[:extend] || [] + ]) + else + igniter + end + end) end defp check_constraints(%{check_constraints: _check_constraints}, true) do @@ -138,7 +138,7 @@ defmodule AshPostgres.ResourceGenerator do """ end - defp skip_unique_indexes(%{indexes: indexes}) do + defp skip_unique_indexes(%{table_name: table_name, indexes: indexes}) do indexes |> Enum.filter(fn %{unique?: unique?, columns: columns} -> unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) @@ -150,12 +150,12 @@ defmodule AshPostgres.ResourceGenerator do indexes -> """ - skip_unique_indexes [#{Enum.map_join(indexes, ",", &":#{&1.name}")}] + skip_unique_indexes [#{Enum.map_join(indexes, ",", &":#{&1.identity_name}")}] """ end end - defp identity_index_names(%{indexes: indexes}) do + defp identity_index_names(%{table_name: table_name, indexes: indexes}) do indexes |> Enum.filter(fn %{unique?: unique?, columns: columns} -> unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) @@ -167,19 +167,19 @@ defmodule AshPostgres.ResourceGenerator do indexes -> indexes |> Enum.map_join(", ", fn index -> - "#{index.name}: \"#{index.name}\"" + "#{index.identity_name}: \"#{index.name}\"" end) |> then(&"identity_index_names [#{&1}]") end end - defp add_identities(str, %{indexes: indexes}) do + defp add_identities(str, %{table_name: table_name, indexes: indexes}) do indexes |> Enum.filter(fn %{unique?: unique?, columns: columns} -> unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) end) |> Enum.map(fn index -> - name = index.name + name = index.identity_name fields = "[" <> Enum.map_join(index.columns, ", ", &":#{&1}") <> "]" diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index fe9fa24c..607e2ae5 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -48,7 +48,16 @@ defmodule AshPostgres.ResourceGenerator.Spec do defmodule Index do @moduledoc false - defstruct [:name, :columns, :unique?, :nulls_distinct, :where_clause, :using, :include] + defstruct [ + :name, + :columns, + :unique?, + :nulls_distinct, + :where_clause, + :using, + :include, + :identity_name + ] end defmodule CheckConstraint do @@ -196,7 +205,8 @@ defmodule AshPostgres.ResourceGenerator.Spec do contype = 'c' AND conrelid::regclass::text = $1 """, - [spec.table_name] + [spec.table_name], + log: false ) attribute = Enum.find(spec.attributes, & &1.primary_key?) || Enum.at(spec.attributes, 0) @@ -290,9 +300,18 @@ defmodule AshPostgres.ResourceGenerator.Spec do nil end + identity_name = + index_name + |> String.trim_leading(spec.table_name <> "_") + |> String.trim_leading("unique_") + |> String.replace("_unique_", "_") + |> String.trim_trailing("_index") + |> String.replace("_index_", "_") + [ %Index{ name: index_name, + identity_name: identity_name, columns: Enum.uniq(columns), unique?: is_unique, include: include, From 088bf9d11289cfcdbcfcccc3b59f81cd7b686e1b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 12:54:43 -0400 Subject: [PATCH 0681/1215] chore: release version v2.3.1 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae1e8191..b3d07dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.3.1](https://github.com/ash-project/ash_postgres/compare/v2.3.0...v2.3.1) (2024-09-05) + + + + +### Improvements: + +* better imported index names + +* add `--extend` option, forwarded to generated resource + ## [v2.3.0](https://github.com/ash-project/ash_postgres/compare/v2.2.5...v2.3.0) (2024-09-05) diff --git a/mix.exs b/mix.exs index bc665a66..6227db81 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.3.0" + @version "2.3.1" def project do [ From 20486d19681ded546740d393c05222bbdc89fd15 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 12:55:37 -0400 Subject: [PATCH 0682/1215] chore: update changelog --- CHANGELOG.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d07dbf..8f8f5e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,25 +7,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.3.1](https://github.com/ash-project/ash_postgres/compare/v2.3.0...v2.3.1) (2024-09-05) - - - ### Improvements: -* better imported index names +- [`mix ash_postgres.gen.migrations`] better imported index names -* add `--extend` option, forwarded to generated resource +- [`mix ash_postgres.gen.migrations`] add `--extend` option, forwarded to generated resource ## [v2.3.0](https://github.com/ash-project/ash_postgres/compare/v2.2.5...v2.3.0) (2024-09-05) - - - ### Features: -* `mix ash_postgres.gen.resources` - -* `mix ash_postgres.gen.resources` +- [`mix ash_postgres.gen.resources`] Add `mix ash_postgres.gen.resources` for importing tables from an existing database as resources ## [v2.2.5](https://github.com/ash-project/ash_postgres/compare/v2.2.4...v2.2.5) (2024-09-04) From 845c6bbdaa554ebc8047d33dabca8aff815f842f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 12:57:15 -0400 Subject: [PATCH 0683/1215] chore: fix build --- lib/resource_generator/resource_generator.ex | 6 +++--- test/resource_generator_test.exs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index d29b5800..294ff1d1 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -138,7 +138,7 @@ defmodule AshPostgres.ResourceGenerator do """ end - defp skip_unique_indexes(%{table_name: table_name, indexes: indexes}) do + defp skip_unique_indexes(%{indexes: indexes}) do indexes |> Enum.filter(fn %{unique?: unique?, columns: columns} -> unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) @@ -155,7 +155,7 @@ defmodule AshPostgres.ResourceGenerator do end end - defp identity_index_names(%{table_name: table_name, indexes: indexes}) do + defp identity_index_names(%{indexes: indexes}) do indexes |> Enum.filter(fn %{unique?: unique?, columns: columns} -> unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) @@ -173,7 +173,7 @@ defmodule AshPostgres.ResourceGenerator do end end - defp add_identities(str, %{table_name: table_name, indexes: indexes}) do + defp add_identities(str, %{indexes: indexes}) do indexes |> Enum.filter(fn %{unique?: unique?, columns: columns} -> unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index 50bfa7db..0a9b755a 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.RelWithParentFilterTest do +defmodule AshPostgres.ResourceGeenratorTests do use AshPostgres.RepoCase, async: false setup do From 8d22e6ed65380a7a31923c718ac6ee8e3d900ca2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:04:44 -0400 Subject: [PATCH 0684/1215] chore(deps): bump the production-dependencies group with 2 updates (#379) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash` from 3.4.2 to 3.4.4 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.2...v3.4.4) Updates `ash_sql` from 0.2.31 to 0.2.32 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.31...v0.2.32) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 6410f216..d8e97c6b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.2", "17544d04a1ed5fdc7fc61e9fbb491418c322c73c9cb0c00836c0f80070d4e09a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df5a851797a82a7fa9a1348a0c79bf206f77ce1e3047ec00ce4bfdf733e9459d"}, - "ash_sql": {:hex, :ash_sql, "0.2.31", "721521e073d706169ebb0e68535422c1920580b29829fe949fb679c8674a9691", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "e5f578be31f5fa5af8dd1cb27b01b7b1864ef1414472293ce3a4851290cb69b1"}, + "ash": {:hex, :ash, "3.4.4", "1745bf9ee2eb4351540a60e14c85ca4ac6afb3924a5625f0b1a4dfdeeed30512", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "38484f04dd19cb985e767bed30a99e5cbb83642db26cc81d8f7e6d0e6ed31e9c"}, + "ash_sql": {:hex, :ash_sql, "0.2.32", "de99255becfb9daa7991c18c870e9f276bb372acda7eda3e05c3e2ff2ca8922e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "43773bcd33d21319c11804d76fe11f1a1b7c8faba7aaedeab6f55fde3d2405db"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, From 2d7879ff151e09cc37aea347c0fa3c411b558947 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Sep 2024 13:17:07 -0400 Subject: [PATCH 0685/1215] chore: singularize resource name --- lib/resource_generator/resource_generator.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 294ff1d1..d6772ba2 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -21,6 +21,7 @@ defmodule AshPostgres.ResourceGenerator do |> Enum.map(fn %{table_name: table} = spec -> resource = table + |> Inflex.singularize() |> Macro.camelize() |> then(&Module.concat([domain, &1])) From ba29ed7c70df3a751daf69547a36ac42f40bbe5d Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Fri, 6 Sep 2024 21:19:48 +0200 Subject: [PATCH 0686/1215] chore: fix PG_VERSION parsing in tests (#382) Allow passing a bare integer as PG_VERSION, which will be interpreted as a major version --- test/support/test_repo.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index c12dad79..4f01c091 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -14,8 +14,14 @@ defmodule AshPostgres.TestRepo do def min_pg_version do case System.get_env("PG_VERSION") do - nil -> %Version{major: 16, minor: 0, patch: 0} - version -> Version.parse!(version) + nil -> + %Version{major: 16, minor: 0, patch: 0} + + version -> + case Integer.parse(version) do + {major, ""} -> %Version{major: major, minor: 0, patch: 0} + _ -> Version.parse!(version) + end end end From 68fff263916b617962592c056587807656e10c56 Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Fri, 6 Sep 2024 21:20:50 +0200 Subject: [PATCH 0687/1215] chore: failing test for constraint violation with stream strategy (#381) This is probably a problem in Ash but I didn't know a simple way to make the ETS data layer fail similarly to a constraint violation --- .../post_permalinks/20240906170759.json | 58 +++++++++++++++++++ .../20240906170759_migrate_resources38.exs | 33 +++++++++++ test/bulk_destroy_test.exs | 36 ++++++++++++ test/support/domain.ex | 1 + test/support/resources/permalink.ex | 33 +++++++++++ test/support/resources/post.ex | 2 + 6 files changed, 163 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json create mode 100644 priv/test_repo/migrations/20240906170759_migrate_resources38.exs create mode 100644 test/support/resources/permalink.ex diff --git a/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json b/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json new file mode 100644 index 00000000..6a4201eb --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json @@ -0,0 +1,58 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "post_permalinks_post_id_fkey", + "on_delete": "nothing", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "post_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "2B9B1A4022280ABA644CD2AEB55F765ECC0DFAD3E41F247BFA953ECCB0DCB2F9", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "post_permalinks" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240906170759_migrate_resources38.exs b/priv/test_repo/migrations/20240906170759_migrate_resources38.exs new file mode 100644 index 00000000..f1211140 --- /dev/null +++ b/priv/test_repo/migrations/20240906170759_migrate_resources38.exs @@ -0,0 +1,33 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources38 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 + create table(:post_permalinks, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + + add( + :post_id, + references(:posts, + column: :id, + name: "post_permalinks_post_id_fkey", + type: :uuid, + prefix: "public", + on_delete: :nothing + ), + null: false + ) + end + end + + def down do + drop(constraint(:post_permalinks, "post_permalinks_post_id_fkey")) + + drop(table(:post_permalinks)) + end +end diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index dafc2926..e30b62de 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -1,5 +1,6 @@ defmodule AshPostgres.BulkDestroyTest do use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Permalink alias AshPostgres.Test.Post require Ash.Expr @@ -68,4 +69,39 @@ defmodule AshPostgres.BulkDestroyTest do assert [] = Ash.read!(Post) end + + test "bulk destroys errors on constraint violation" do + post = Ash.create!(Post, %{title: "fred"}) + Ash.create!(Permalink, %{post_id: post.id}) + + assert %Ash.BulkResult{ + status: :error, + error_count: 1, + errors: [%Ash.Error.Invalid{}] + } = + Post + |> Ash.read!() + |> Ash.bulk_destroy(:destroy, %{}, + return_records?: true, + return_errors?: true + ) + end + + test "bulk destroys returns error on constraint violation with strategy stream" do + post = Ash.create!(Post, %{title: "fred"}) + Ash.create!(Permalink, %{post_id: post.id}) + + assert %Ash.BulkResult{ + status: :error, + error_count: 1, + errors: [%Ash.Error.Invalid{}] + } = + Post + |> Ash.read!() + |> Ash.bulk_destroy(:destroy, %{}, + strategy: :stream, + return_records?: true, + return_errors?: true + ) + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index a1037a47..353ca4d6 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -18,6 +18,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Manager) resource(AshPostgres.Test.Entity) resource(AshPostgres.Test.TempEntity) + resource(AshPostgres.Test.Permalink) resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) resource(AshPostgres.Test.StatefulPostFollower) diff --git a/test/support/resources/permalink.ex b/test/support/resources/permalink.ex new file mode 100644 index 00000000..21df7959 --- /dev/null +++ b/test/support/resources/permalink.ex @@ -0,0 +1,33 @@ +defmodule AshPostgres.Test.Permalink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + actions do + default_accept(:*) + + defaults([:create, :read]) + end + + attributes do + uuid_primary_key(:id) + end + + relationships do + belongs_to :post, AshPostgres.Test.Post do + public?(true) + allow_nil?(false) + attribute_writable?(true) + end + end + + postgres do + table "post_permalinks" + repo AshPostgres.TestRepo + + references do + reference :post, on_delete: :nothing + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index fabbdc6d..31b22721 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -504,6 +504,8 @@ defmodule AshPostgres.Test.Post do has_many(:views, AshPostgres.Test.PostView) do public?(true) end + + has_many(:permalinks, AshPostgres.Test.Permalink) end validations do From b144ee193753feaa51b5423006c174e9de9bba11 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Sep 2024 13:40:21 -0400 Subject: [PATCH 0688/1215] improvement: ensure `Repo` is started after telemetry improvement: update to latest igniter functions --- lib/data_layer.ex | 2 +- lib/igniter.ex | 4 ++-- lib/mix/tasks/ash_postgres.install.ex | 21 ++++++++++++++------- mix.lock | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 200ee3af..53e3967e 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2984,7 +2984,7 @@ defmodule AshPostgres.DataLayer do repo = case options[:repo] do nil -> - Igniter.Code.Module.module_name("Repo") + Igniter.Code.Module.module_name(igniter, "Repo") repo -> Igniter.Code.Module.parse(repo) diff --git a/lib/igniter.ex b/lib/igniter.ex index 63fe886d..8ac3f29e 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -76,8 +76,8 @@ defmodule AshPostgres.Igniter do case list_repos(igniter) do {igniter, []} -> if generate do - repo = Igniter.Code.Module.module_name("Repo") - otp_app = Igniter.Project.Application.app_name() + repo = Igniter.Code.Module.module_name(igniter, "Repo") + otp_app = Igniter.Project.Application.app_name(igniter) igniter = Igniter.Code.Module.create_module(igniter, repo, default_repo_contents(otp_app)) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index e7a44ed8..d8b93fc8 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -6,8 +6,8 @@ defmodule Mix.Tasks.AshPostgres.Install do use Igniter.Mix.Task def igniter(igniter, _argv) do - repo = Igniter.Code.Module.module_name("Repo") - otp_app = Igniter.Project.Application.app_name() + repo = Igniter.Code.Module.module_name(igniter, "Repo") + otp_app = Igniter.Project.Application.app_name(igniter) igniter |> Igniter.Project.Formatter.import_dep(:ash_postgres) @@ -17,7 +17,14 @@ defmodule Mix.Tasks.AshPostgres.Install do |> configure_runtime(otp_app, repo) |> configure_test(otp_app, repo) |> setup_data_case() - |> Igniter.Project.Application.add_new_child(repo) + |> Igniter.Project.Application.add_new_child(repo, + after: fn mod -> + case Module.split(mod) do + [_, "Telemetry"] -> true + _ -> false + end + end + ) |> Spark.Igniter.prepend_to_section_order(:"Ash.Resource", [:postgres]) |> Ash.Igniter.codegen("initialize") end @@ -221,23 +228,23 @@ defmodule Mix.Tasks.AshPostgres.Install do using do quote do - alias #{inspect(Igniter.Code.Module.module_name("Repo"))} + alias #{inspect(Igniter.Code.Module.module_name(igniter, "Repo"))} import Ecto import Ecto.Changeset import Ecto.Query - import #{inspect(Igniter.Code.Module.module_name("DataCase"))} + import #{inspect(Igniter.Code.Module.module_name(igniter, "DataCase"))} end end setup tags do - pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Code.Module.module_name("Repo"))}, shared: not tags[:async]) + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Code.Module.module_name(igniter, "Repo"))}, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) :ok end | - module_name = Igniter.Code.Module.module_name("DataCase") + module_name = Igniter.Code.Module.module_name(igniter, "DataCase") igniter |> Igniter.Code.Module.find_and_update_or_create_module( diff --git a/mix.lock b/mix.lock index d8e97c6b..aa2e8729 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.24", "791a91650ffab9d66b9a3011c66491f767577ad55c363f820cc188554207ee6f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "2e1d336534c6129bae0db043fae650303b96974c0488c290191d6d4c61ec9a9f"}, + "igniter": {:hex, :igniter, "0.3.30", "89c414e4e3d3d58a29133ad2480d9037abf8881a83e6aa5a753307e5db5f74b5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "072e441e1bf5102b2516fe75113174c35c381ea3674e389eb6e9f0fb476bbc70"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From b35b97654763bf2a7040208168d1195fc3d24a38 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Sep 2024 20:09:32 -0400 Subject: [PATCH 0689/1215] improvement: support upcoming `action_select` options --- lib/data_layer.ex | 43 +- mix.lock | 8 +- .../test_repo/posts/20240910180107.json | 459 ++++++++++++++++++ .../20240910180107_migrate_resources39.exs | 21 + test/bulk_create_test.exs | 15 + test/select_test.exs | 8 + test/support/resources/post.ex | 18 + test/test_helper.exs | 2 +- 8 files changed, 560 insertions(+), 14 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20240910180107.json create mode 100644 priv/test_repo/migrations/20240910180107_migrate_resources39.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 53e3967e..ef61be85 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1368,10 +1368,17 @@ defmodule AshPostgres.DataLayer do query = if options[:return_records?] do {:ok, query} = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([row], row) - |> add_calculations(options[:calculations] || [], resource) + if options[:action_select] do + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], struct(row, ^options[:action_select])) + |> add_calculations(options[:calculations] || [], resource) + else + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + |> add_calculations(options[:calculations] || [], resource) + end query else @@ -1626,10 +1633,19 @@ defmodule AshPostgres.DataLayer do query = if options[:return_records?] do {:ok, query} = - query - |> Ecto.Query.exclude(:select) - |> Ecto.Query.select([row], row) - |> add_calculations(options[:calculations] || [], resource) + case options[:action_select] do + nil -> + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], row) + |> add_calculations(options[:calculations] || [], resource) + + action_select -> + query + |> Ecto.Query.exclude(:select) + |> Ecto.Query.select([row], struct(row, ^action_select)) + |> add_calculations(options[:calculations] || [], resource) + end query else @@ -1717,7 +1733,13 @@ defmodule AshPostgres.DataLayer do opts = if options.return_records? do - Keyword.put(opts, :returning, true) + returning = + case options[:action_select] do + nil -> true + fields -> fields + end + + Keyword.put(opts, :returning, returning) else opts end @@ -1956,6 +1978,7 @@ defmodule AshPostgres.DataLayer do case bulk_create(resource, [changeset], %{ single?: true, tenant: Map.get(changeset, :to_tenant, changeset.tenant), + action_select: changeset.action_select, return_records?: true }) do {:ok, [result]} -> @@ -2539,6 +2562,7 @@ defmodule AshPostgres.DataLayer do tenant: changeset.tenant, identity: identity, upsert_keys: keys, + action_select: changeset.action_select, upsert_fields: upsert_fields, return_records?: true }) do @@ -2733,6 +2757,7 @@ defmodule AshPostgres.DataLayer do case update_query(query, changeset, resource, %{ return_records?: true, + action_select: changeset.action_select, calculations: [] }) do {:ok, []} -> diff --git a/mix.lock b/mix.lock index aa2e8729..ee8fc935 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.4", "1745bf9ee2eb4351540a60e14c85ca4ac6afb3924a5625f0b1a4dfdeeed30512", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "38484f04dd19cb985e767bed30a99e5cbb83642db26cc81d8f7e6d0e6ed31e9c"}, + "ash": {:hex, :ash, "3.4.8", "8c35c1e401044d05474c0e1a3209c74afb7ac955243085489242cade24d31906", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e8dd3eb1f3aa45a75265c784df95ce261a3fad7f0d07400d316e981e14ed10f0"}, "ash_sql": {:hex, :ash_sql, "0.2.32", "de99255becfb9daa7991c18c870e9f276bb372acda7eda3e05c3e2ff2ca8922e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "43773bcd33d21319c11804d76fe11f1a1b7c8faba7aaedeab6f55fde3d2405db"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -9,7 +9,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, - "ecto": {:hex, :ecto, "3.12.2", "bae2094f038e9664ce5f089e5f3b6132a535d8b018bd280a485c2f33df5c0ce1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e67c70f3a71c6afe80d946d3ced52ecc57c53c9829791bfff1830ff5a1f0c"}, + "ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"}, "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.30", "89c414e4e3d3d58a29133ad2480d9037abf8881a83e6aa5a753307e5db5f74b5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "072e441e1bf5102b2516fe75113174c35c381ea3674e389eb6e9f0fb476bbc70"}, + "igniter": {:hex, :igniter, "0.3.35", "edd8db6234db7639eb2f954b45ab23db98b1fd0a940850f58db8900912a908bf", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d2826d94d8c851e2bc8b920766815b3df4b184cab05eae8423e965363a2e02b1"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -38,7 +38,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.23", "78f0a1b0b713a91ad556fe9dc19ec92d977aaa0803cce2e255d90e58b9045c2a", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a354b5cd7c3f021e3cd1da5a033b7643fe7b3c71c96b96d9f500a742f40c94db"}, + "spark": {:hex, :spark, "2.2.26", "1701f388a9cfb2e27cd037b6f4b72a999e49bdb2d2f946bdbde8a991ce42c499", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "7a57860e4d15ab2e395dffeac617f3ee64d371b47f7b3d718a8d535d75cc7556"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/priv/resource_snapshots/test_repo/posts/20240910180107.json b/priv/resource_snapshots/test_repo/posts/20240910180107.json new file mode 100644 index 00000000..065dc1e9 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240910180107.json @@ -0,0 +1,459 @@ +{ + "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": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "702922F4C474FB9294FE47B61DF92D6A5D2D6586F696B7F5B05493CEA4C485D0", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240910180107_migrate_resources39.exs b/priv/test_repo/migrations/20240910180107_migrate_resources39.exs new file mode 100644 index 00000000..adf27660 --- /dev/null +++ b/priv/test_repo/migrations/20240910180107_migrate_resources39.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources39 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(:posts) do + add(:not_selected_by_default, :text) + end + end + + def down do + alter table(:posts) do + remove(:not_selected_by_default) + end + end +end diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index d7a2c544..f4e7ceb6 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -14,6 +14,21 @@ defmodule AshPostgres.BulkCreateTest do |> Ash.read!() end + test "bulk creates perform before action hooks" do + Ash.bulk_create!( + [%{title: "before_action"}, %{title: "before_action"}], + Post, + :create_with_before_action, + return_errors?: true, + return_records?: true + ) + + assert [%{title: "before_action"}, %{title: "before_action"}] = + Post + |> Ash.Query.sort(:title) + |> Ash.read!() + end + test "bulk creates can be streamed" do assert [{:ok, %{title: "fred"}}, {:ok, %{title: "george"}}] = Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, diff --git a/test/select_test.exs b/test/select_test.exs index 1d30aaea..219fdc77 100644 --- a/test/select_test.exs +++ b/test/select_test.exs @@ -12,4 +12,12 @@ defmodule AshPostgres.SelectTest do assert [%{title: %Ash.NotLoaded{}}] = Ash.read!(Ash.Query.select(Post, :id)) end + + test "values not selected in a changeset are not present in the response" do + assert %{title: %Ash.NotLoaded{}} = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.Changeset.select([]) + |> Ash.create!() + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 31b22721..f727dfc7 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -269,6 +269,20 @@ defmodule AshPostgres.Test.Post do ) end + defmodule HasBeforeAction do + use Ash.Resource.Change + + def change(changeset, _, _) do + Ash.Changeset.before_action(changeset, fn changeset -> + Ash.Changeset.force_change_attribute(changeset, :title, "before_action") + end) + end + end + + create :create_with_before_action do + change(HasBeforeAction) + end + create :upsert_with_filter do upsert?(true) upsert_identity(:uniq_if_contains_foo) @@ -331,6 +345,10 @@ defmodule AshPostgres.Test.Post do source(:title_column) end + attribute :not_selected_by_default, :string do + select_by_default?(false) + end + attribute(:datetime, AshPostgres.TimestamptzUsec, public?: true) attribute(:score, :integer, public?: true) attribute(:public, :boolean, public?: true) diff --git a/test/test_helper.exs b/test/test_helper.exs index 6a184284..10ae3340 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.start(capture_log: true) +# ExUnit.start(capture_log: true) ExUnit.configure(stacktrace_depth: 100) AshPostgres.TestRepo.start_link() From 8779250fdea3e1cd58639734d273addba6bb1a44 Mon Sep 17 00:00:00 2001 From: Ahmed Kamal Date: Wed, 11 Sep 2024 22:57:22 +1000 Subject: [PATCH 0690/1215] docs: fix typo in timestamptz (#384) --- lib/types/timestamptz.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/timestamptz.ex b/lib/types/timestamptz.ex index 6388b2fb..dfac2d4f 100644 --- a/lib/types/timestamptz.ex +++ b/lib/types/timestamptz.ex @@ -6,7 +6,7 @@ defmodule AshPostgres.Timestamptz do The basic reason `timestamptz` exists is to guarantee that the precise moment in time is stored as microseconds since January 1st, 2000 in UTC. This guarantee eliminates many time arithmetic problems, and ensures portability. - It does not actually store a timezone, in spite of the name. As far as Elixir/Ecto is concerned, it it always of type `DateTime` and set to UTC. Using this type ensures Postgres internally uses the same contract as Ecto's `:utc_datetime`, which is to always store `DateTime` in UTC. This is especially helpful if you need to do complex time arithmetic in SQL fragments, or build reports/materialized views that use localized time formatting. + It does not actually store a timezone, in spite of the name. As far as Elixir/Ecto is concerned, it is always of type `DateTime` and set to UTC. Using this type ensures Postgres internally uses the same contract as Ecto's `:utc_datetime`, which is to always store `DateTime` in UTC. This is especially helpful if you need to do complex time arithmetic in SQL fragments, or build reports/materialized views that use localized time formatting. Using this type ubiquitously in your schemas is particularly beneficial for consistency, and this is currently [under consideration](https://github.com/ash-project/ash_postgres/issues/264) as a configuration option for the default datetime storage type. From ec3273f1638a1c41133409531e842566022eb3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Thu, 12 Sep 2024 15:11:26 +0200 Subject: [PATCH 0691/1215] feat: Implement Ltree Type (#385) --- lib/resource_generator/spec.ex | 1 + lib/types/ltree.ex | 221 ++++++++ mix.exs | 1 + .../test_repo/extensions.json | 7 +- .../test_repo/posts/20240911225320.json | 479 ++++++++++++++++++ .../20240911225319_install_1_extensions.exs | 19 + .../20240911225320_migrate_resources40.exs | 23 + test/ltree_test.exs | 176 +++++++ test/support/resources/post.ex | 7 + test/support/test_repo.ex | 2 +- 10 files changed, 932 insertions(+), 4 deletions(-) create mode 100644 lib/types/ltree.ex create mode 100644 priv/resource_snapshots/test_repo/posts/20240911225320.json create mode 100644 priv/test_repo/migrations/20240911225319_install_1_extensions.exs create mode 100644 priv/test_repo/migrations/20240911225320_migrate_resources40.exs create mode 100644 test/ltree_test.exs diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 607e2ae5..ee1c3194 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -903,6 +903,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do defp type("tsvector"), do: {:ok, AshPostgres.Tsvector} defp type("uuid"), do: {:ok, :uuid} defp type("citext"), do: {:ok, :ci_string} + defp type("ltree"), do: {:ok, AshPostgres.Ltree} defp type(_), do: :error defp set_sensitive(attributes) do diff --git a/lib/types/ltree.ex b/lib/types/ltree.ex new file mode 100644 index 00000000..0d6ad79e --- /dev/null +++ b/lib/types/ltree.ex @@ -0,0 +1,221 @@ +defmodule AshPostgres.Ltree do + @constraints [ + escape?: [ + type: :boolean, + doc: """ + Escape the ltree segments to make it possible to include characters that + are either `.` (the separation character) or any other unsupported + character like `-` (Postgres <= 15). + + If the option is enabled, any characters besides `[0-9a-zA-Z]` will be + replaced with `_[HEX Ascii Code]`. + + Additionally the type will no longer take strings as user input since + it's impossible to decide between `.` being a separator or part of a + segment. + + If the option is disabled, any string will be relayed directly to + postgres. If the segments are provided as a list, they can't contain `.` + since postgres would split the segment. + """ + ], + min_length: [ + type: :non_neg_integer, + doc: "A minimum length for the tree segments." + ], + max_length: [ + type: :non_neg_integer, + doc: "A maximum length for the tree segments." + ] + ] + + @moduledoc """ + Ash Type for [postgres `ltree`](https://www.postgresql.org/docs/current/ltree.html), + a hierarchical tree-like data type. + + ## Postgres Extension + + To be able to use the `ltree` type, you'll have to enable the postgres `ltree` + extension first. + + See `m:AshPostgres.Repo#module-installed-extensions` + + ## Constraints + + #{Spark.Options.docs(@constraints)} + """ + + use Ash.Type + + @type t() :: [segment()] + @type segment() :: String.t() + + @impl Ash.Type + def storage_type(_constraints), do: :ltree + + @impl Ash.Type + def constraints, do: @constraints + + @impl Ash.Type + def matches_type?(list, _constraints) when is_list(list), do: true + + def matches_type?(binary, constraints) when is_binary(binary), + do: not Keyword.get(constraints, :escape?, false) + + def matches_type?(_ltree, _constraints), do: false + + @impl Ash.Type + def generator(constraints) do + segment = + if constraints[:escape?], + do: StreamData.string(:utf8, min_length: 1), + else: StreamData.string(:alphanumeric, min_length: 1) + + StreamData.list_of(segment, Keyword.take(constraints, [:min_length, :max_length])) + end + + @impl Ash.Type + def apply_constraints(nil, _constraints), do: {:ok, nil} + + def apply_constraints(ltree, constraints) do + segment_validation = + Enum.reduce_while(ltree, :ok, fn segment, :ok -> + cond do + segment == "" -> + {:halt, {:error, message: "Ltree segments can't be empty.", value: segment}} + + not String.valid?(segment) -> + {:halt, + {:error, message: "Ltree segments must be valid UTF-8 strings.", value: segment}} + + String.contains?(segment, ".") and !constraints[:escape?] -> + {:halt, + {:error, + message: ~S|Ltree segments can't contain "." if :escape? is not enabled.|, + value: segment}} + + true -> + {:cont, :ok} + end + end) + + with :ok <- segment_validation do + cond do + constraints[:min_length] && length(ltree) < constraints[:min_length] -> + {:error, message: "must have %{min} or more items", min: constraints[:min_length]} + + constraints[:max_length] && length(ltree) > constraints[:max_length] -> + {:error, message: "must have %{max} or less items", max: constraints[:max_length]} + + true -> + :ok + end + end + end + + @impl Ash.Type + def cast_input(nil, _constraints), do: {:ok, nil} + + def cast_input(string, constraints) when is_binary(string) do + if constraints[:escape?] do + {:error, "String input casting is not supported when the :escape? constraint is enabled"} + else + string |> String.split(".") |> cast_input(constraints) + end + end + + def cast_input(list, _constraints) when is_list(list) do + if Enum.all?(list, &is_binary/1) do + {:ok, list} + else + {:error, "Ltree segments must be strings. #{inspect(list)} provided."} + end + end + + def cast_input(_ltree, _constraints), do: :error + + @impl Ash.Type + def cast_stored(nil, _constraints), do: {:ok, nil} + + def cast_stored(ltree, constraints) when is_binary(ltree) do + segments = + ltree + |> String.split(".", trim: true) + |> then( + if constraints[:escape?] do + fn segments -> Enum.map(segments, &unescape_segment/1) end + else + & &1 + end + ) + + {:ok, segments} + end + + def cast_stored(_ltree, _constraints), do: :error + + @impl Ash.Type + def dump_to_native(nil, _constraints), do: {:ok, nil} + + def dump_to_native(ltree, constraints) when is_list(ltree) do + if constraints[:escape?] do + {:ok, Enum.map_join(ltree, ".", &escape_segment/1)} + else + {:ok, Enum.join(ltree, ".")} + end + end + + def dump_to_native(_ltree, _constraints), do: :error + + @doc """ + Get shared root of given ltrees. + + ## Examples + + iex> Ltree.shared_root(["1", "2"], ["1", "1"]) + ["1"] + + iex> Ltree.shared_root(["1", "2"], ["2", "1"]) + [] + + """ + @spec shared_root(ltree1 :: t(), ltree2 :: t()) :: t() + def shared_root(ltree1, ltree2) do + ltree1 + |> List.myers_difference(ltree2) + |> case do + [{:eq, shared} | _] -> shared + _other -> [] + end + end + + @spec escape_segment(segment :: String.t()) :: String.t() + defp escape_segment(segment) + defp escape_segment(<<>>), do: <<>> + + defp escape_segment(<>) + when letter in ?0..?9 + when letter in ?a..?z + when letter in ?A..?Z, + do: <> + + defp escape_segment(<>) do + escape_code = letter |> Integer.to_string(16) |> String.pad_leading(2, "0") + <> + end + + @spec unescape_segment(segment :: String.t()) :: String.t() + defp unescape_segment(segment) + defp unescape_segment(<<>>), do: <<>> + + defp unescape_segment(<>) + when letter in ?0..?9 + when letter in ?a..?z + when letter in ?A..?Z, + do: <> + + defp unescape_segment(<>) do + {letter, ""} = Integer.parse(<>, 16) + <> + end +end diff --git a/mix.exs b/mix.exs index 6227db81..f0ce841c 100644 --- a/mix.exs +++ b/mix.exs @@ -134,6 +134,7 @@ defmodule AshPostgres.MixProject do AshPostgres.Statement ], Types: [ + AshPostgres.Ltree, AshPostgres.Type, AshPostgres.Tsquery, AshPostgres.Tsvector, diff --git a/priv/resource_snapshots/test_repo/extensions.json b/priv/resource_snapshots/test_repo/extensions.json index 08191fb2..557d0ea8 100644 --- a/priv/resource_snapshots/test_repo/extensions.json +++ b/priv/resource_snapshots/test_repo/extensions.json @@ -1,10 +1,11 @@ { + "ash_functions_version": 4, "installed": [ "ash-functions", "uuid-ossp", "pg_trgm", "citext", - "demo-functions_v1" - ], - "ash_functions_version": 4 + "demo-functions_v1", + "ltree" + ] } \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20240911225320.json b/priv/resource_snapshots/test_repo/posts/20240911225320.json new file mode 100644 index 00000000..0eb03ca9 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240911225320.json @@ -0,0 +1,479 @@ +{ + "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": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "1CCE2AC7D78FB347E152BE62184DF908C651671C1BDF9EC9AACB6CFA44264451", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240911225319_install_1_extensions.exs b/priv/test_repo/migrations/20240911225319_install_1_extensions.exs new file mode 100644 index 00000000..0f2eb12b --- /dev/null +++ b/priv/test_repo/migrations/20240911225319_install_1_extensions.exs @@ -0,0 +1,19 @@ +defmodule AshPostgres.TestRepo.Migrations.Install1Extensions20240911225317 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute("CREATE EXTENSION IF NOT EXISTS \"ltree\"") + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + # execute("DROP EXTENSION IF EXISTS \"ltree\"") + end +end diff --git a/priv/test_repo/migrations/20240911225320_migrate_resources40.exs b/priv/test_repo/migrations/20240911225320_migrate_resources40.exs new file mode 100644 index 00000000..1e1cd754 --- /dev/null +++ b/priv/test_repo/migrations/20240911225320_migrate_resources40.exs @@ -0,0 +1,23 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources40 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(:posts) do + add(:ltree_unescaped, :ltree) + add(:ltree_escaped, :ltree) + end + end + + def down do + alter table(:posts) do + remove(:ltree_escaped) + remove(:ltree_unescaped) + end + end +end diff --git a/test/ltree_test.exs b/test/ltree_test.exs new file mode 100644 index 00000000..cbd54ab1 --- /dev/null +++ b/test/ltree_test.exs @@ -0,0 +1,176 @@ +defmodule AshPostgres.LtreeTest do + use AshPostgres.RepoCase, async: true + use ExUnitProperties + + alias AshPostgres.Ltree + alias AshPostgres.Test.Post + + require Ash.Query + + doctest AshPostgres.Ltree + + describe inspect(&Ltree.storage_type/1) do + test "correct" do + assert :ltree = Ltree.storage_type([]) + end + end + + describe inspect(&Ltree.matches_type?/2) do + test "correct" do + assert Ltree.matches_type?(["1", "2"], []) + assert Ltree.matches_type?("1.2", []) + refute Ltree.matches_type?("1.2", escape?: true) + end + end + + describe inspect(&Ltee.generator/1) do + property "generates valid ltrees" do + check all( + constraints <- constraints_generator(), + ltree <- Ltree.generator(constraints) + ) do + assert :ok = Ltree.apply_constraints(ltree, constraints) + end + end + end + + describe inspect(&Ltree.apply_constraints/2) do + test "checks min length" do + assert :ok = Ltree.apply_constraints(["1", "2"], min_length: 2) + + assert {:error, [message: "must have %{min} or more items", min: 2]} = + Ltree.apply_constraints(["1"], min_length: 2) + end + + test "checks max length" do + assert :ok = Ltree.apply_constraints(["1", "2"], max_length: 2) + + assert {:error, [message: "must have %{max} or less items", max: 1]} = + Ltree.apply_constraints(["1", "2"], max_length: 1) + end + + test "checks UTF-8" do + assert :ok = Ltree.apply_constraints(["1", "2"], []) + + assert {:error, + [message: "Ltree segments must be valid UTF-8 strings.", value: <<0xFFFF::16>>]} = + Ltree.apply_constraints([<<0xFFFF::16>>, "2"], []) + end + + test "checks empty string" do + assert {:error, [message: "Ltree segments can't be empty.", value: ""]} = + Ltree.apply_constraints(["", "2"], []) + end + + test ~S|can't contain "." when escape? is not enabled| do + assert :ok = Ltree.apply_constraints(["1", "2"], []) + + assert {:error, + [ + message: ~S|Ltree segments can't contain "." if :escape? is not enabled.|, + value: "1.2" + ]} = Ltree.apply_constraints(["1.2"], []) + + assert :ok = Ltree.apply_constraints(["1.2"], escape?: true) + end + end + + describe inspect(&Ltree.cast_input/2) do + test "casts nil" do + assert {:ok, nil} = Ltree.cast_input(nil, []) + end + + test "casts list" do + assert {:ok, ["1", "2"]} = Ltree.cast_input(["1", "2"], []) + end + + test "casts binary if escaped?" do + assert {:ok, ["1", "2"]} = Ltree.cast_input("1.2", []) + + assert {:error, + "String input casting is not supported when the :escape? constraint is enabled"} = + Ltree.cast_input("1.2", escape?: true) + end + end + + describe inspect(&Ltree.cast_stored/2) do + test "casts nil" do + assert {:ok, nil} = Ltree.cast_stored(nil, []) + end + + test "casts binary" do + assert {:ok, ["1", "2"]} = Ltree.cast_stored("1.2", []) + end + + test "unescapes segments" do + assert {:ok, ["1.", "2"]} = Ltree.cast_stored("1_2E.2", escape?: true) + end + end + + describe inspect(&Ltree.dump_to_native/2) do + test "dumps nil" do + assert {:ok, nil} = Ltree.dump_to_native(nil, []) + end + + test "dumps list" do + assert {:ok, "1.2"} = Ltree.dump_to_native(["1", "2"], []) + end + + test "escapes segments" do + assert {:ok, "1_2E.2"} = Ltree.dump_to_native(["1.", "2"], escape?: true) + end + end + + describe inspect(&Ltree.shared_root/2) do + test "works when they share a root" do + assert ["1"] = Ltree.shared_root(["1", "1"], ["1", "2"]) + end + + test "returns empty list if they do not share a root" do + assert [] = Ltree.shared_root(["1", "2"], ["2", "1"]) + end + end + + describe "escape/unescape" do + property "escape |> unescape results in same value" do + check all(ltree <- Ltree.generator(escape?: true)) do + assert {:ok, stored} = Ltree.dump_to_native(ltree, escape?: true) + assert {:ok, loaded} = Ltree.cast_stored(stored, escape?: true) + + assert loaded == ltree + end + end + end + + describe "integration" do + test "can serialize / underialize to db" do + post = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "title", + ltree_unescaped: ["1", "2"], + ltree_escaped: ["1.", "2"] + }) + |> Ash.create!() + + assert %Post{id: id, ltree_unescaped: ["1", "2"], ltree_escaped: ["1.", "2"]} = post + + assert %Post{id: ^id} = + Post |> Ash.Query.filter(ltree_unescaped == ["1", "2"]) |> Ash.read_one!() + + assert %Post{id: ^id} = + Post |> Ash.Query.filter(ltree_escaped == ["1.", "2"]) |> Ash.read_one!() + end + end + + defp constraints_generator do + [ + StreamData.tuple({StreamData.constant(:escape?), StreamData.boolean()}), + StreamData.tuple({StreamData.constant(:min_length), StreamData.non_negative_integer()}), + StreamData.tuple({StreamData.constant(:max_length), StreamData.positive_integer()}) + ] + |> StreamData.one_of() + |> StreamData.list_of(max_length: 3) + |> StreamData.filter(&(not (&1[:min_length] > &1[:max_length]))) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index f727dfc7..cbef1c10 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -387,6 +387,13 @@ defmodule AshPostgres.Test.Post do constraints(nil_items?: true) end + attribute(:ltree_unescaped, AshPostgres.Ltree, + constraints: [min_length: 1, max_length: 10], + public?: true + ) + + attribute(:ltree_escaped, AshPostgres.Ltree, constraints: [escape?: true], public?: true) + create_timestamp(:created_at, writable?: true, public?: true) update_timestamp(:updated_at, diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 4f01c091..c272a2e5 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -8,7 +8,7 @@ defmodule AshPostgres.TestRepo do end def installed_extensions do - ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- + ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- Application.get_env(:ash_postgres, :no_extensions, []) end From e2b648a9e47bb41b4e6d1bb970b0928ede5cc043 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 09:37:12 -0400 Subject: [PATCH 0692/1215] improvement: remove LEAKPROOF from function to prevent migration issues chore: add some tests to show calculation w/ fragment --- lib/migration_generator/ash_functions.ex | 2 +- test/calculation_test.exs | 4 ++++ test/support/resources/post.ex | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 609bb487..369173cb 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -230,7 +230,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); $$ LANGUAGE SQL - IMMUTABLE PARALLEL SAFE STRICT LEAKPROOF; + IMMUTABLE PARALLEL SAFE STRICT; \"\"\") """ end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 38cc70f8..0301868d 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -312,6 +312,10 @@ defmodule AshPostgres.CalculationTest do refute Ash.calculate!(Post, :author_has_post_with_follower_named_fred, refs: %{id: post.id}) end + test "calculation works with simple fragments" do + Post.upper_title!("example") + end + test "calculations that refer to aggregates can be authorized" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index cbef1c10..615d1c31 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -409,6 +409,8 @@ defmodule AshPostgres.Test.Post do define(:increment_score, args: [{:optional, :amount}]) define(:destroy) define(:update_constrained_int, args: [:amount]) + + define_calculation(:upper_title, args: [:title]) end relationships do @@ -542,6 +544,8 @@ defmodule AshPostgres.Test.Post do calculations do calculate(:upper_thing, :string, expr(fragment("UPPER(?)", uniq_on_upper))) + calculate(:upper_title, :string, expr(fragment("UPPER(?)", title))) + calculate( :author_has_post_with_follower_named_fred, :boolean, From 045796f91688e87aa21cbc666e58629686666596 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 15:01:21 -0400 Subject: [PATCH 0693/1215] improvement: update ash to latest version chore: add some tests, use new `Igniter.Test` --- mix.exs | 2 +- mix.lock | 4 +- test/bulk_destroy_test.exs | 2 +- test/resource_generator_test.exs | 67 +++++++++++++++----------------- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/mix.exs b/mix.exs index f0ce841c..7f4c9a85 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.2")}, + {:ash, ash_version("~> 3.4 and >= 3.4.9")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, {:igniter, "~> 0.3 and >= 0.3.6"}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index ee8fc935..6ac7b8d0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.8", "8c35c1e401044d05474c0e1a3209c74afb7ac955243085489242cade24d31906", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e8dd3eb1f3aa45a75265c784df95ce261a3fad7f0d07400d316e981e14ed10f0"}, + "ash": {:hex, :ash, "3.4.9", "17ad3a8a37d17f1d5db93f26a53fa979fa19046b741140afef526cf22b306f56", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.35 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e3c24fd0a249356abc5da8deb4265e40906f75f0aadec779767fb828712d446d"}, "ash_sql": {:hex, :ash_sql, "0.2.32", "de99255becfb9daa7991c18c870e9f276bb372acda7eda3e05c3e2ff2ca8922e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "43773bcd33d21319c11804d76fe11f1a1b7c8faba7aaedeab6f55fde3d2405db"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -38,7 +38,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.26", "1701f388a9cfb2e27cd037b6f4b72a999e49bdb2d2f946bdbde8a991ce42c499", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "7a57860e4d15ab2e395dffeac617f3ee64d371b47f7b3d718a8d535d75cc7556"}, + "spark": {:hex, :spark, "2.2.27", "213447d22c5ccdfa9d77bc4c58ba2f5ace99dadd9b974e52a374fa58320ab9e4", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "59ca9129fe707dcfec768591d1d2582c6cc8062fb6329df4837cba8a88392b0b"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index e30b62de..ddc9b65a 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -13,7 +13,7 @@ defmodule AshPostgres.BulkDestroyTest do test "bulk destroys destroy everything pertaining to the query" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) - Ash.bulk_destroy!(Post, :destroy, %{}) + Ash.bulk_destroy!(Post, :destroy, %{}, return_records?: true) assert Ash.read!(Post) == [] end diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index 0a9b755a..e2b655ed 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -1,6 +1,8 @@ defmodule AshPostgres.ResourceGeenratorTests do use AshPostgres.RepoCase, async: false + import Igniter.Test + setup do AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS example_table") @@ -15,41 +17,34 @@ defmodule AshPostgres.ResourceGeenratorTests do end test "a resource is generated from a table" do - resource = - Igniter.new() - |> Igniter.compose_task("ash_postgres.gen.resources", [ - "MyApp.Accounts", - "--tables", - "example_table", - "--yes" - ]) - |> Igniter.prepare_for_write() - |> Map.get(:rewrite) - |> Rewrite.source!("lib/my_app/accounts/example_table.ex") - |> Rewrite.Source.get(:content) - - assert String.trim(resource) == - String.trim(""" - defmodule MyApp.Accounts.ExampleTable do - use Ash.Resource, - domain: MyApp.Accounts, - data_layer: AshPostgres.DataLayer - - postgres do - table "example_table" - repo AshPostgres.TestRepo - end - - attributes do - uuid_primary_key(:id) - attribute(:name, :string) - attribute(:age, :integer) - - attribute :email, :string do - sensitive?(true) - end - end - end - """) + test_project() + |> Igniter.compose_task("ash_postgres.gen.resources", [ + "MyApp.Accounts", + "--tables", + "example_table", + "--yes" + ]) + |> assert_creates("lib/my_app/accounts/example_table.ex", """ + defmodule MyApp.Accounts.ExampleTable do + use Ash.Resource, + domain: MyApp.Accounts, + data_layer: AshPostgres.DataLayer + + postgres do + table("example_table") + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string) + attribute(:age, :integer) + + attribute :email, :string do + sensitive?(true) + end + end + end + """) end end From 43f6880308cb93f5bf222fa85f6da2bbfa1d979a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 15:11:25 -0400 Subject: [PATCH 0694/1215] chore: fix build --- lib/resource_generator/spec.ex | 2 ++ mix.lock | 2 -- test/support/resources/post.ex | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index ee1c3194..9c67d3fc 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -453,6 +453,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do |> set_defaults_and_generated() end + # sobelow_skip ["DOS.StringToAtom"] defp set_defaults_and_generated(attributes) do Enum.map(attributes, fn attribute -> attribute = @@ -823,6 +824,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do end) end + # sobelow_skip ["RCE.CodeModule", "DOS.StringToAtom"] defp get_type(attribute, opts) do result = if opts[:yes?] do diff --git a/mix.lock b/mix.lock index 6ac7b8d0..f94861bc 100644 --- a/mix.lock +++ b/mix.lock @@ -29,7 +29,6 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, - "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, @@ -44,7 +43,6 @@ "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, } diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 615d1c31..7f1385a9 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -270,6 +270,7 @@ defmodule AshPostgres.Test.Post do end defmodule HasBeforeAction do + @moduledoc false use Ash.Resource.Change def change(changeset, _, _) do From ad636fcbfdb948b5753025a0f71e9c425b9bec31 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 15:11:36 -0400 Subject: [PATCH 0695/1215] chore: release version v2.4.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f8f5e4c..25520753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.0](https://github.com/ash-project/ash_postgres/compare/v2.3.1...v2.4.0) (2024-09-13) + + + + +### Features: + +* Implement Ltree Type (#385) + +### Improvements: + +* update ash to latest version + +* remove LEAKPROOF from function to prevent migration issues + +* support upcoming `action_select` options + +* ensure `Repo` is started after telemetry + +* update to latest igniter functions + ## [v2.3.1](https://github.com/ash-project/ash_postgres/compare/v2.3.0...v2.3.1) (2024-09-05) ### Improvements: diff --git a/mix.exs b/mix.exs index 7f4c9a85..9fdb63a0 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.3.1" + @version "2.4.0" def project do [ From d7a8083ff95081ba074eaf8b128e5d3a05148646 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 15:12:00 -0400 Subject: [PATCH 0696/1215] chore: update changelog --- CHANGELOG.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25520753..4587e3ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,24 +7,21 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ## [v2.4.0](https://github.com/ash-project/ash_postgres/compare/v2.3.1...v2.4.0) (2024-09-13) - - - ### Features: -* Implement Ltree Type (#385) +- Implement Ltree Type (#385) ### Improvements: -* update ash to latest version +- update ash to latest version -* remove LEAKPROOF from function to prevent migration issues +- remove LEAKPROOF from function to prevent migration issues -* support upcoming `action_select` options +- support upcoming `action_select` options -* ensure `Repo` is started after telemetry +- ensure `Repo` is started after telemetry in igniter installer -* update to latest igniter functions +- update to latest igniter functions ## [v2.3.1](https://github.com/ash-project/ash_postgres/compare/v2.3.0...v2.3.1) (2024-09-05) From eb3329b4f300203aff2bc7302f72f85a947de489 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 15:51:59 -0400 Subject: [PATCH 0697/1215] fix: match on table schema as well as table name --- lib/resource_generator/spec.ex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 9c67d3fc..fffaf97e 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -972,7 +972,14 @@ defmodule AshPostgres.ResourceGenerator.Spec do information_schema.columns c ON t.table_name = c.table_name JOIN pg_attribute a - ON a.attrelid = (SELECT oid FROM pg_class WHERE relname = t.table_name AND relkind = 'r') + ON a.attrelid = ( + SELECT c.oid + FROM pg_class c + JOIN pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = t.table_name + AND n.nspname = t.table_schema + AND c.relkind = 'r' + ) AND a.attname = c.column_name AND a.attnum > 0 WHERE @@ -1009,7 +1016,14 @@ defmodule AshPostgres.ResourceGenerator.Spec do information_schema.columns c ON t.table_name = c.table_name JOIN pg_attribute a - ON a.attrelid = (SELECT oid FROM pg_class WHERE relname = t.table_name AND relkind = 'r') + ON a.attrelid = ( + SELECT c.oid + FROM pg_class c + JOIN pg_namespace n ON c.relnamespace = n.oid + WHERE c.relname = t.table_name + AND n.nspname = t.table_schema + AND c.relkind = 'r' + ) AND a.attname = c.column_name AND a.attnum > 0 WHERE From 3c0872ab94c9b3290df438bb769cbcd3f55e6d22 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 13 Sep 2024 19:10:54 -0400 Subject: [PATCH 0698/1215] chore: update igniter and fix deprecation warning --- lib/mix/tasks/ash_postgres.install.ex | 2 +- mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index d8b93fc8..5126c6d4 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -252,7 +252,7 @@ defmodule Mix.Tasks.AshPostgres.Install do default_data_case_contents, # do nothing if already exists fn zipper -> {:ok, zipper} end, - path: Igniter.Code.Module.proper_location(module_name, "test/support") + path: Igniter.Project.Module.proper_location(igniter, module_name, "test/support") ) end diff --git a/mix.exs b/mix.exs index 9fdb63a0..5da440dc 100644 --- a/mix.exs +++ b/mix.exs @@ -166,7 +166,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.4 and >= 3.4.9")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, - {:igniter, "~> 0.3 and >= 0.3.6"}, + {:igniter, "~> 0.3 and >= 0.3.36"}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index f94861bc..32e44fbd 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.35", "edd8db6234db7639eb2f954b45ab23db98b1fd0a940850f58db8900912a908bf", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d2826d94d8c851e2bc8b920766815b3df4b184cab05eae8423e965363a2e02b1"}, + "igniter": {:hex, :igniter, "0.3.36", "7dffb41e8c25dac3de8a0947c4973dc3db3cd25f394fd87ac334fe18a725f291", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5a493222cbf4e3cf0106cd090c93a1f61fa4df958b7d00e03a836e8a67a4bab2"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 3afbae2c806ac94529ba49c123ed7ef384bb0e9b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 15 Sep 2024 20:56:51 -0400 Subject: [PATCH 0699/1215] fix: ensure that returning is not an empty list --- lib/data_layer.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ef61be85..79052e73 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1736,6 +1736,7 @@ defmodule AshPostgres.DataLayer do returning = case options[:action_select] do nil -> true + [] -> Ash.Resource.Info.primary_key(resource) fields -> fields end From 448237d8d1f283d9d63d9253deefcecbf672c7f9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Sep 2024 08:39:56 -0400 Subject: [PATCH 0700/1215] test: add test for data seeding --- mix.lock | 6 +++--- test/create_test.exs | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 32e44fbd..8695e923 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.36", "7dffb41e8c25dac3de8a0947c4973dc3db3cd25f394fd87ac334fe18a725f291", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5a493222cbf4e3cf0106cd090c93a1f61fa4df958b7d00e03a836e8a67a4bab2"}, + "igniter": {:hex, :igniter, "0.3.37", "ad4ec1c0d73dedf5514ac52c5e93d5daa64bf4037a17088a9a7f4d44133a5846", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "727b74a67df63cbe4c21a99707e02c50f4b7740c93cd3431fa9184a863eb064c"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -32,12 +32,12 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, - "reactor": {:hex, :reactor, "0.9.1", "082f8e9b1fd7586c0a016c2fb533835fec7eaef5ffb0263abb4473106c20b1ca", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7191ddf95fdd2b65770a57a2e38dd502a94909e51ac8daf497330e67fc032dc3"}, + "reactor": {:hex, :reactor, "0.10.0", "1206113c21ba69b889e072b2c189c05a7aced523b9c3cb8dbe2dab7062cb699a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4003c33e4c8b10b38897badea395e404d74d59a31beb30469a220f2b1ffe6457"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.27", "213447d22c5ccdfa9d77bc4c58ba2f5ace99dadd9b974e52a374fa58320ab9e4", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "59ca9129fe707dcfec768591d1d2582c6cc8062fb6329df4837cba8a88392b0b"}, + "spark": {:hex, :spark, "2.2.29", "a52733ff72b05a674e48d3ca7a4172fe7bec81e9116069da8b4db19030d581d9", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "111a0dadbb27537c7629bc03ac56fcab15056ab0b9ad985084b9adcdb48836c8"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/create_test.exs b/test/create_test.exs index 0f461dfc..23fe3533 100644 --- a/test/create_test.exs +++ b/test/create_test.exs @@ -2,6 +2,10 @@ defmodule AshPostgres.CreateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post + test "seeding data works" do + Ash.Seed.seed!(%Post{title: "fred"}) + end + test "creates insert" do assert {:ok, %Post{}} = Post From d472f622f910ec6b14b1e03cbb2c7775e207d0cb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Sep 2024 08:55:32 -0400 Subject: [PATCH 0701/1215] chore: fix ash_postgres igniter --- lib/mix/tasks/ash_postgres.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 5126c6d4..e0af615b 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -252,7 +252,7 @@ defmodule Mix.Tasks.AshPostgres.Install do default_data_case_contents, # do nothing if already exists fn zipper -> {:ok, zipper} end, - path: Igniter.Project.Module.proper_location(igniter, module_name, "test/support") + path: Igniter.Project.Module.proper_location(igniter, module_name, :test_support) ) end From 2101ab3cd9a60314791e67e4ff260e03600ec559 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Sep 2024 10:20:27 -0400 Subject: [PATCH 0702/1215] chore: add basic tests for install task --- test/mix/tasks/ash_postgres.install_test.exs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/mix/tasks/ash_postgres.install_test.exs diff --git a/test/mix/tasks/ash_postgres.install_test.exs b/test/mix/tasks/ash_postgres.install_test.exs new file mode 100644 index 00000000..eb687a0d --- /dev/null +++ b/test/mix/tasks/ash_postgres.install_test.exs @@ -0,0 +1,13 @@ +defmodule Mix.Tasks.AshPostgres.InstallTest do + use ExUnit.Case + + import Igniter.Test + + # This is a simple test to ensure that the installation doesnt have + # any errors. We should add better tests here, though. + test "installation does not fail" do + test_project() + |> Igniter.compose_task("ash_postgres.install") + |> assert_creates("lib/test/repo.ex") + end +end From 873a1f8983a16de89ebb6a669b141e3f1cbe52c1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Sep 2024 10:21:50 -0400 Subject: [PATCH 0703/1215] chore: release version v2.4.1 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4587e3ea..da0eeb29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.1](https://github.com/ash-project/ash_postgres/compare/v2.4.0...v2.4.1) (2024-09-16) + + + + +### Bug Fixes: + +* ensure that returning is not an empty list + +* match on table schema as well as table name + ## [v2.4.0](https://github.com/ash-project/ash_postgres/compare/v2.3.1...v2.4.0) (2024-09-13) ### Features: diff --git a/mix.exs b/mix.exs index 5da440dc..5ea543be 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.0" + @version "2.4.1" def project do [ From ee0c2a0f452b8cf8b5f379ad993f7ed7546ab064 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Sep 2024 17:04:15 -0400 Subject: [PATCH 0704/1215] test: add test for behavior fixed in ash --- test/calculation_test.exs | 39 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 1 + 2 files changed, 40 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 0301868d..8c0f2678 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -5,6 +5,45 @@ defmodule AshPostgres.CalculationTest do require Ash.Query import Ash.Expr + test "a calculation that references a first optimizable aggregate can be sorted on" do + author1 = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "abc" + }) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match", author_id: author1.id}) + |> Ash.create!() + + author2 = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "def" + }) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{title: "match", author_id: author2.id}) + |> Ash.create!() + + post1_id = post.id + post2_id = post2.id + + assert [%{id: ^post2_id}, %{id: ^post1_id}] = + Post + |> Ash.Query.sort(author_first_name_ref_agg_calc: :desc) + |> Ash.read!() + + assert [%{id: ^post1_id}, %{id: ^post2_id}] = + Post + |> Ash.Query.sort(author_first_name_ref_agg_calc: :asc) + |> Ash.read!() + end + test "an expression calculation can be filtered on" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 7f1385a9..d6b8e745 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -692,6 +692,7 @@ defmodule AshPostgres.Test.Post do ) calculate(:author_first_name_calc, :string, expr(author.first_name)) + calculate(:author_first_name_ref_agg_calc, :string, expr(author_first_name)) calculate(:author_profile_description_from_agg, :string, expr(author_profile_description)) end From bc26951c41fa1c2484742de912ac97b46182f084 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Sep 2024 06:52:21 -0400 Subject: [PATCH 0705/1215] test: add test for optimistic lock --- .../test_repo/posts/20240918104740.json | 489 ++++++++++++++++++ .../20240918104740_migrate_resources41.exs | 21 + test/support/resources/post.ex | 7 + test/update_test.exs | 28 + 4 files changed, 545 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20240918104740.json create mode 100644 priv/test_repo/migrations/20240918104740_migrate_resources41.exs diff --git a/priv/resource_snapshots/test_repo/posts/20240918104740.json b/priv/resource_snapshots/test_repo/posts/20240918104740.json new file mode 100644 index 00000000..9b2de037 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240918104740.json @@ -0,0 +1,489 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "AB47CA6F1EB3D500856075592A4D436CC4F323885E1D49E35089C07877F069DF", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240918104740_migrate_resources41.exs b/priv/test_repo/migrations/20240918104740_migrate_resources41.exs new file mode 100644 index 00000000..118fe8fc --- /dev/null +++ b/priv/test_repo/migrations/20240918104740_migrate_resources41.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources41 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(:posts) do + add(:version, :bigint, null: false, default: 1) + end + end + + def down do + alter table(:posts) do + remove(:version) + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d6b8e745..aadcee06 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -327,6 +327,11 @@ defmodule AshPostgres.Test.Post do message: "You cannot select less than two." ) end + + update :optimistic_lock do + accept([:title]) + change(optimistic_lock(:version)) + end end identities do @@ -341,6 +346,8 @@ defmodule AshPostgres.Test.Post do attributes do uuid_primary_key(:id, writable?: true) + attribute(:version, :integer, allow_nil?: false, default: 1) + attribute(:title, :string) do public?(true) source(:title_column) diff --git a/test/update_test.exs b/test/update_test.exs index 59897db3..9afb32ab 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -34,4 +34,32 @@ defmodule AshPostgres.UpdateTest do } ) end + + test "can optimist lock" do + Logger.configure(level: :debug) + + Post + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create!() + |> then(fn record -> + Ash.Query.filter(Post, id == ^record.id) + end) + |> Ash.bulk_update( + :optimistic_lock, + %{ + title: "george" + } + ) + + Post + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create!() + |> Ash.Changeset.for_update( + :optimistic_lock, + %{ + title: "george" + } + ) + |> Ash.update!() + end end From 6eda5fae9c13ae553215e9442e1cfa69184e3b03 Mon Sep 17 00:00:00 2001 From: James Harton Date: Thu, 19 Sep 2024 11:56:28 +1200 Subject: [PATCH 0706/1215] fix: trim input before passing to `String.to_integer/1`. (#389) --- lib/migration_generator/migration_generator.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7b9de7a2..f75670cb 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -727,6 +727,7 @@ defmodule AshPostgres.MigrationGenerator do opts |> prompt(message) + |> String.trim() |> String.to_integer() end From ebd7a71d51a119b9794fbaa08db2368ff15f977f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:33:46 -0400 Subject: [PATCH 0707/1215] chore(deps): bump the production-dependencies group with 2 updates (#390) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.9 to 3.4.17 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.9...v3.4.17) Updates `igniter` from 0.3.37 to 0.3.39 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.37...v0.3.39) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 8695e923..2596b036 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.9", "17ad3a8a37d17f1d5db93f26a53fa979fa19046b741140afef526cf22b306f56", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.35 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e3c24fd0a249356abc5da8deb4265e40906f75f0aadec779767fb828712d446d"}, + "ash": {:hex, :ash, "3.4.17", "2f2b817a9ea88ae68c4845b27a9ac0fc60f507cf4cc7ad3a3b614b1bdd619783", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54eba5f478b7d1f4570479cdf747fa65e993acebb8d6bf2a7e8a48b4e0519ae2"}, "ash_sql": {:hex, :ash_sql, "0.2.32", "de99255becfb9daa7991c18c870e9f276bb372acda7eda3e05c3e2ff2ca8922e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "43773bcd33d21319c11804d76fe11f1a1b7c8faba7aaedeab6f55fde3d2405db"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.37", "ad4ec1c0d73dedf5514ac52c5e93d5daa64bf4037a17088a9a7f4d44133a5846", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "727b74a67df63cbe4c21a99707e02c50f4b7740c93cd3431fa9184a863eb064c"}, + "igniter": {:hex, :igniter, "0.3.39", "e59b56836b9b22c6b76b1b9661c60a996eca7bbb3b0a22f7a888cd1c77d43419", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "0e794a273b53442a6fb223a65cd9800648d6e2f8fdf8556bcf74d0af793f4bd4"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 103158829fb8af9f7e08a954118de3ad13718824 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 19 Sep 2024 13:50:43 -0400 Subject: [PATCH 0708/1215] improvement: set a name for generated migrations --- lib/mix/tasks/ash_postgres.gen.resources.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 5e270f8e..097a45fe 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -135,7 +135,9 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do if options[:no_migrations] do igniter else - Igniter.add_task(igniter, "ash_postgres.generate_migrations", migration_opts) + Igniter.add_task(igniter, "ash_postgres.generate_migrations", [ + "import_resources" | migration_opts + ]) end end) end From 6219e8ba71096072ee10c76396ac3dfd8c9c539c Mon Sep 17 00:00:00 2001 From: James Harton Date: Mon, 23 Sep 2024 09:47:20 +1200 Subject: [PATCH 0709/1215] fix(installer): use correct module name in the `DataCase` moduledocs. (#393) --- lib/mix/tasks/ash_postgres.install.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index e0af615b..c5c71fe3 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -208,6 +208,8 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp setup_data_case(igniter) do + module_name = Igniter.Code.Module.module_name(igniter, "DataCase") + default_data_case_contents = ~s| @moduledoc """ This module defines the setup for tests requiring @@ -220,7 +222,7 @@ defmodule Mix.Tasks.AshPostgres.Install do we enable the SQL sandbox, so changes done to the database are reverted at the end of every test. If you are using PostgreSQL, you can even run database tests asynchronously - by setting `use AshHq.DataCase, async: true`, although + by setting `use #{inspect(module_name)}, async: true`, although this option is not recommended for other databases. """ @@ -244,8 +246,6 @@ defmodule Mix.Tasks.AshPostgres.Install do end | - module_name = Igniter.Code.Module.module_name(igniter, "DataCase") - igniter |> Igniter.Code.Module.find_and_update_or_create_module( module_name, From 0351f52211ca0969fdc4713b94b81864d3017f7d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 23 Sep 2024 14:27:52 -0400 Subject: [PATCH 0710/1215] improvement: prompt for minimum pg version improvement: adjust mix task aliases to be used with `ash_postgres` closes #391 --- lib/igniter.ex | 60 +++++++++- lib/mix/helpers.ex | 8 +- .../tasks/ash_postgres.generate_migrations.ex | 2 +- lib/mix/tasks/ash_postgres.install.ex | 110 ++++++++++++++---- mix.exs | 2 +- mix.lock | 2 +- test/mix/tasks/ash_postgres.install_test.exs | 2 +- 7 files changed, 152 insertions(+), 34 deletions(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 8ac3f29e..5d0a0053 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -2,13 +2,14 @@ defmodule AshPostgres.Igniter do @moduledoc "Codemods and utilities for working with AshPostgres & Igniter" @doc false - def default_repo_contents(otp_app) do + def default_repo_contents(otp_app, name, opts \\ []) do + min_pg_version = get_min_pg_version(name, opts) + """ use AshPostgres.Repo, otp_app: #{inspect(otp_app)} def min_pg_version do - # Adjust this according to your postgres version - %Version{major: 16, minor: 0, patch: 0} + %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor || 0}, patch: #{min_pg_version.patch || 0}} end def installed_extensions do @@ -80,7 +81,12 @@ defmodule AshPostgres.Igniter do otp_app = Igniter.Project.Application.app_name(igniter) igniter = - Igniter.Code.Module.create_module(igniter, repo, default_repo_contents(otp_app)) + Igniter.Code.Module.create_module( + igniter, + repo, + default_repo_contents(otp_app, repo), + opts + ) {igniter, repo} else @@ -110,4 +116,50 @@ defmodule AshPostgres.Igniter do ) end) end + + @doc false + def get_min_pg_version(name, opts) do + if opts[:yes] do + %Version{major: 16, minor: 0, patch: 0} + else + lead_in = """ + Generating #{inspect(name)} + + What is the minimum postgres version you will be using? + + AshPostgres uses this information when generating queries and migrations + to choose the best available features for your version of postgres. + """ + + format_request = + """ + Please enter the version in the format major.minor.patch (e.g. 13.4.0) + + Default: 16.0.0 + + ❯ + """ + + prompt = + if opts[:invalid_loop?] do + format_request + else + "#{lead_in}\n\n#{format_request}" + end + + prompt + |> String.trim_trailing() + |> Mix.shell().prompt() + |> String.trim() + |> case do + "" -> "16.0.0" + input -> input + end + |> Version.parse() + |> case do + {:ok, version} -> version + :error -> get_min_pg_version(name, Keyword.put(opts, :invalid_loop?, true)) + end + end + end end diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 9168165b..6858ec47 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -1,6 +1,6 @@ defmodule AshPostgres.Mix.Helpers do @moduledoc false - def domains!(opts, args, error_on_no_domains? \\ true) do + def domains!(opts, args) do apps = if apps_paths = Mix.Project.apps_paths() do apps_paths |> Map.keys() |> Enum.sort() @@ -30,11 +30,7 @@ defmodule AshPostgres.Mix.Helpers do |> Enum.map(&ensure_compiled(&1, args)) |> case do [] -> - if error_on_no_domains? do - raise "must supply the --domains argument, or set `config :my_app, ash_domains: [...]` in config" - else - [] - end + [] domains -> domains diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 68f92b55..eedaf280 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -100,7 +100,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do ] ) - domains = AshPostgres.Mix.Helpers.domains!(opts, args, false) + domains = AshPostgres.Mix.Helpers.domains!(opts, args) if Enum.empty?(domains) && !opts[:snapshots_only] do IO.warn(""" diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index c5c71fe3..a33986c2 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -5,13 +5,29 @@ defmodule Mix.Tasks.AshPostgres.Install do require Igniter.Code.Function use Igniter.Mix.Task - def igniter(igniter, _argv) do + @impl true + def info(_argv, _source) do + %Igniter.Mix.Task.Info{ + schema: [ + yes: :boolean + ], + aliases: [ + y: :yes + ] + } + end + + @impl true + def igniter(igniter, argv) do repo = Igniter.Code.Module.module_name(igniter, "Repo") otp_app = Igniter.Project.Application.app_name(igniter) + opts = options!(argv) + igniter |> Igniter.Project.Formatter.import_dep(:ash_postgres) - |> setup_repo_module(otp_app, repo) + |> setup_aliases() + |> setup_repo_module(otp_app, repo, opts) |> configure_config(otp_app, repo) |> configure_dev(otp_app, repo) |> configure_runtime(otp_app, repo) @@ -29,6 +45,47 @@ defmodule Mix.Tasks.AshPostgres.Install do |> Ash.Igniter.codegen("initialize") end + defp setup_aliases(igniter) do + is_ecto_setup = &Igniter.Code.Common.nodes_equal?(&1, "ecto.setup") + + is_ecto_create_or_migrate = + fn zipper -> + Igniter.Code.Common.nodes_equal?(zipper, "ecto.create --quiet") or + Igniter.Code.Common.nodes_equal?(zipper, "ecto.create") or + Igniter.Code.Common.nodes_equal?(zipper, "ecto.migrate --quiet") or + Igniter.Code.Common.nodes_equal?(zipper, "ecto.migrate") + end + + igniter + |> Igniter.Project.TaskAliases.modify_existing_alias( + "test", + &Igniter.Code.List.remove_from_list(&1, is_ecto_create_or_migrate) + ) + |> Igniter.Project.TaskAliases.modify_existing_alias( + "test", + &Igniter.Code.List.replace_in_list( + &1, + is_ecto_setup, + "ash.setup" + ) + ) + |> Igniter.Project.TaskAliases.add_alias("test", ["ash.setup --quiet", "test"], + if_exists: {:prepend, "ash.setup --quiet"} + ) + |> run_seeds_on_setup() + end + + defp run_seeds_on_setup(igniter) do + if Igniter.exists?(igniter, "priv/repo/seeds.exs") do + Igniter.Project.TaskAliases.add_alias(igniter, "ash.setup", [ + "ash.setup", + "run priv/repo/seeds.exs" + ]) + else + igniter + end + end + defp configure_config(igniter, otp_app, repo) do Igniter.Project.Config.configure( igniter, @@ -256,26 +313,38 @@ defmodule Mix.Tasks.AshPostgres.Install do ) end - defp setup_repo_module(igniter, otp_app, repo) do - Igniter.Code.Module.find_and_update_or_create_module( - igniter, + defp setup_repo_module(igniter, otp_app, repo, opts) do + {exists?, igniter} = Igniter.Project.Module.module_exists?(igniter, repo) + + if exists? do + Igniter.Project.Module.find_and_update_module!( + igniter, + repo, + fn zipper -> + {:ok, + zipper + |> set_otp_app(otp_app) + |> Sourceror.Zipper.top() + |> use_ash_postgres_instead_of_ecto() + |> Sourceror.Zipper.top() + |> remove_adapter_option()} + end + ) + else + Igniter.Project.Module.create_module( + igniter, + repo, + AshPostgres.Igniter.default_repo_contents(otp_app, repo, opts) + ) + end + |> Igniter.Project.Module.find_and_update_module!( repo, - AshPostgres.Igniter.default_repo_contents(otp_app), - fn zipper -> - {:ok, - zipper - |> set_otp_app(otp_app) - |> Sourceror.Zipper.top() - |> use_ash_postgres_instead_of_ecto() - |> Sourceror.Zipper.top() - |> remove_adapter_option()} - end + &configure_installed_extensions_function/1 ) |> Igniter.Code.Module.find_and_update_module!( repo, - &configure_installed_extensions_function/1 + &configure_min_pg_version_function(&1, repo, opts) ) - |> Igniter.Code.Module.find_and_update_module!(repo, &configure_min_pg_version_function/1) end defp use_ash_postgres_instead_of_ecto(zipper) do @@ -347,17 +416,18 @@ defmodule Mix.Tasks.AshPostgres.Install do end end - defp configure_min_pg_version_function(zipper) do + defp configure_min_pg_version_function(zipper, repo, opts) do case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do {:ok, zipper} -> {:ok, zipper} _ -> + min_pg_version = AshPostgres.Igniter.get_min_pg_version(repo, opts) + {:ok, Igniter.Code.Common.add_code(zipper, """ def min_pg_version do - # Adjust this according to your postgres version - %Version{major: 16, minor: 0, patch: 0} + %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor || 0}, patch: #{min_pg_version.patch || 0}} end """)} end diff --git a/mix.exs b/mix.exs index 5ea543be..f402e868 100644 --- a/mix.exs +++ b/mix.exs @@ -166,7 +166,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.4 and >= 3.4.9")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, - {:igniter, "~> 0.3 and >= 0.3.36"}, + {:igniter, "~> 0.3 and >= 0.3.42"}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 2596b036..82a4c6ef 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.39", "e59b56836b9b22c6b76b1b9661c60a996eca7bbb3b0a22f7a888cd1c77d43419", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "0e794a273b53442a6fb223a65cd9800648d6e2f8fdf8556bcf74d0af793f4bd4"}, + "igniter": {:hex, :igniter, "0.3.42", "29ab08de9ba4316a6eb0ac73cd4481b3d2c4e799a647b667faafe5c988487e1b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d6bb222d7c6f29a9fb2461a93109da5787e4354928feea2b7a14827c27129115"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, diff --git a/test/mix/tasks/ash_postgres.install_test.exs b/test/mix/tasks/ash_postgres.install_test.exs index eb687a0d..e90b69ee 100644 --- a/test/mix/tasks/ash_postgres.install_test.exs +++ b/test/mix/tasks/ash_postgres.install_test.exs @@ -7,7 +7,7 @@ defmodule Mix.Tasks.AshPostgres.InstallTest do # any errors. We should add better tests here, though. test "installation does not fail" do test_project() - |> Igniter.compose_task("ash_postgres.install") + |> Igniter.compose_task("ash_postgres.install", ["--yes"]) |> assert_creates("lib/test/repo.ex") end end From 5642e0f96d6c64c3bf5991aaa9fd90474dfe723d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 23 Sep 2024 14:38:33 -0400 Subject: [PATCH 0711/1215] chore: fix dialyzer --- lib/igniter.ex | 2 +- lib/mix/tasks/ash_postgres.install.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 5d0a0053..9b37d0d0 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -9,7 +9,7 @@ defmodule AshPostgres.Igniter do use AshPostgres.Repo, otp_app: #{inspect(otp_app)} def min_pg_version do - %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor || 0}, patch: #{min_pg_version.patch || 0}} + %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} end def installed_extensions do diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index a33986c2..dee5368c 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -427,7 +427,7 @@ defmodule Mix.Tasks.AshPostgres.Install do {:ok, Igniter.Code.Common.add_code(zipper, """ def min_pg_version do - %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor || 0}, patch: #{min_pg_version.patch || 0}} + %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} end """)} end From a87d9b4c81e7254cc145f31b630e3928ee315e50 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 23 Sep 2024 15:12:45 -0400 Subject: [PATCH 0712/1215] improvement: add `--repo` option to installer, and warn on clashing existing repo --- lib/mix/tasks/ash_postgres.install.ex | 51 ++++++++++++++++++++------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index dee5368c..f75a7b86 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -9,21 +9,31 @@ defmodule Mix.Tasks.AshPostgres.Install do def info(_argv, _source) do %Igniter.Mix.Task.Info{ schema: [ - yes: :boolean + yes: :boolean, + repo: :string ], aliases: [ - y: :yes + y: :yes, + r: :repo ] } end @impl true def igniter(igniter, argv) do - repo = Igniter.Code.Module.module_name(igniter, "Repo") - otp_app = Igniter.Project.Application.app_name(igniter) - opts = options!(argv) + repo = + case opts[:repo] do + nil -> + Igniter.Code.Module.module_name(igniter, "Repo") + + repo -> + Igniter.Code.Module.parse(repo) + end + + otp_app = Igniter.Project.Application.app_name(igniter) + igniter |> Igniter.Project.Formatter.import_dep(:ash_postgres) |> setup_aliases() @@ -321,13 +331,30 @@ defmodule Mix.Tasks.AshPostgres.Install do igniter, repo, fn zipper -> - {:ok, - zipper - |> set_otp_app(otp_app) - |> Sourceror.Zipper.top() - |> use_ash_postgres_instead_of_ecto() - |> Sourceror.Zipper.top() - |> remove_adapter_option()} + case Igniter.Code.Module.move_to_use(zipper, Ecto.Repo) do + {:ok, _} -> + {:ok, + zipper + |> set_otp_app(otp_app) + |> Sourceror.Zipper.top() + |> use_ash_postgres_instead_of_ecto() + |> Sourceror.Zipper.top() + |> remove_adapter_option()} + + _ -> + case Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo) do + {:ok, _} -> + {:ok, zipper} + + _ -> + {:error, + """ + Repo module #{inspect(repo)} existed, but was not an `Ecto.Repo` or an `AshPostgres.Repo`. + + Please rerun the ash_postgresql installer with the `--repo` option to specify a repo. + """} + end + end end ) else From 10c95086d5a428d806b38656a811173257b5548f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Sep 2024 14:52:55 -0400 Subject: [PATCH 0713/1215] fix: typo of `biging` -> `bigint` fix: altering attributes not properly generating foreign keys in some cases --- .../migration_generator.ex | 52 ++++++++++++------- lib/migration_generator/operation.ex | 2 +- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index f75670cb..5118b4f4 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2199,12 +2199,10 @@ defmodule AshPostgres.MigrationGenerator do [] end - if has_reference?(old_snapshot.multitenancy, old_attribute) and - Map.get(old_attribute, :references) != Map.get(new_attribute, :references) do + if Map.get(old_attribute, :references) != Map.get(new_attribute, :references) do redo_deferrability = - if differently_deferrable?(new_attribute, old_attribute) do - [] - else + if has_reference?(old_snapshot.multitenancy, old_attribute) and + differently_deferrable?(new_attribute, old_attribute) do [ %Operation.AlterDeferrability{ table: snapshot.table, @@ -2213,24 +2211,38 @@ defmodule AshPostgres.MigrationGenerator do direction: :up } ] + else + [] end old_and_alter = - [ - %Operation.DropForeignKey{ - attribute: old_attribute, - table: snapshot.table, - schema: snapshot.schema, - multitenancy: old_snapshot.multitenancy, - direction: :up - }, - %Operation.AlterAttribute{ - new_attribute: new_attribute, - old_attribute: old_attribute, - schema: snapshot.schema, - table: snapshot.table - } - ] ++ redo_deferrability + if has_reference?(old_snapshot.multitenancy, old_attribute) do + [ + %Operation.DropForeignKey{ + attribute: old_attribute, + table: snapshot.table, + schema: snapshot.schema, + multitenancy: old_snapshot.multitenancy, + direction: :up + }, + %Operation.AlterAttribute{ + new_attribute: new_attribute, + old_attribute: old_attribute, + schema: snapshot.schema, + table: snapshot.table + } + ] + else + [ + %Operation.AlterAttribute{ + new_attribute: new_attribute, + old_attribute: old_attribute, + schema: snapshot.schema, + table: snapshot.table + } + ] + end ++ + redo_deferrability if has_reference?(snapshot.multitenancy, new_attribute) do reference_ops = [ diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 4198baa3..930a375b 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -501,7 +501,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do Map.get(old_attribute, :references) != Map.get(attribute, :references) do reference(multitenancy, attribute, schema) else - if attribute.type == :biging and attribute.default == "nil" and attribute.generated? do + if attribute.type == :bigint and attribute.default == "nil" and attribute.generated? do ":bigserial" else inspect(attribute.type) From 6e94a2580ccf6c806ba4423fe7234bdb2b4f5a3e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Sep 2024 14:59:46 -0400 Subject: [PATCH 0714/1215] chore: release version v2.4.2 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da0eeb29..0fc6628e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.2](https://github.com/ash-project/ash_postgres/compare/v2.4.1...v2.4.2) (2024-09-24) + + + + +### Bug Fixes: + +* typo of `biging` -> `bigint` + +* altering attributes not properly generating foreign keys in some cases + +* installer: use correct module name in the `DataCase` moduledocs. (#393) + +* trim input before passing to `String.to_integer/1`. (#389) + +### Improvements: + +* add `--repo` option to installer, and warn on clashing existing repo + +* prompt for minimum pg version + +* adjust mix task aliases to be used with `ash_postgres` + +* set a name for generated migrations + ## [v2.4.1](https://github.com/ash-project/ash_postgres/compare/v2.4.0...v2.4.1) (2024-09-16) diff --git a/mix.exs b/mix.exs index f402e868..49bf272f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.1" + @version "2.4.2" def project do [ From 610b894ac0bf9f45e92ff7035941994359a5b5c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:24:21 -0400 Subject: [PATCH 0715/1215] chore(deps): bump the production-dependencies group with 2 updates (#394) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.17 to 3.4.21 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.17...v3.4.21) Updates `igniter` from 0.3.42 to 0.3.45 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.42...v0.3.45) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 82a4c6ef..d841c49e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.17", "2f2b817a9ea88ae68c4845b27a9ac0fc60f507cf4cc7ad3a3b614b1bdd619783", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54eba5f478b7d1f4570479cdf747fa65e993acebb8d6bf2a7e8a48b4e0519ae2"}, + "ash": {:hex, :ash, "3.4.21", "d97b060c64084613ca8317272864be908d591aaa30671d1b04de41f82f8ce368", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1fedea9b994c4b1d18722d49333fd8f30db4af058c9d56cd8cc438b420e6a6a8"}, "ash_sql": {:hex, :ash_sql, "0.2.32", "de99255becfb9daa7991c18c870e9f276bb372acda7eda3e05c3e2ff2ca8922e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "43773bcd33d21319c11804d76fe11f1a1b7c8faba7aaedeab6f55fde3d2405db"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, - "igniter": {:hex, :igniter, "0.3.42", "29ab08de9ba4316a6eb0ac73cd4481b3d2c4e799a647b667faafe5c988487e1b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d6bb222d7c6f29a9fb2461a93109da5787e4354928feea2b7a14827c27129115"}, + "igniter": {:hex, :igniter, "0.3.45", "f487138ed0c5cbf8f1bdd53360cdc2ac40c18ff379c8a575a7a34e526b4ba846", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fbe663e3f4566fb358c64bdddf0ceb05cf9175cea34cccf45a9aa5532ea967f8"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -37,7 +37,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.29", "a52733ff72b05a674e48d3ca7a4172fe7bec81e9116069da8b4db19030d581d9", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "111a0dadbb27537c7629bc03ac56fcab15056ab0b9ad985084b9adcdb48836c8"}, + "spark": {:hex, :spark, "2.2.30", "811977b274cdad9d7668f934d44e8a013d7d9a059b63bcb6f2afb434dfcec458", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "476f2d463463ae14734f1e0c48cd642090cf3fb4082b8e73ea08118ad0da24ce"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 9c69ed15953d168bdfec31fe8360777c25c555de Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Sep 2024 08:24:35 -0400 Subject: [PATCH 0716/1215] chore: clean up test name & remove logger --- test/update_test.exs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/update_test.exs b/test/update_test.exs index 9afb32ab..e67db4ae 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -35,9 +35,7 @@ defmodule AshPostgres.UpdateTest do ) end - test "can optimist lock" do - Logger.configure(level: :debug) - + test "can optimistic lock" do Post |> Ash.Changeset.for_create(:create, %{title: "fred"}) |> Ash.create!() From fd7b9015e8b74d419dd207b62985ffcd469b9583 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Sep 2024 11:12:49 -0400 Subject: [PATCH 0717/1215] docs: clarify getting started guide --- .../get-started-with-ash-postgres.md | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index db8e1c15..4ad0c498 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -148,9 +148,24 @@ And finally, add the repo to your application ... ``` -#### Add AshPostgres to our resources + + +## Adding AshPostgres to your resources + + + +### With Igniter + +You can add `AshPostgres` to a resource with `mix ash.patch.extend Your.Resource.Name postgres`. For example: + +```sh +mix ash.patch.extend Helpdesk.Support.Ticket postgres +mix ash.patch.extend Helpdesk.Support.Representative postgres +``` + +### Manually -Now we can add the data layer to our resources. The basic configuration for a resource requires the `d:AshPostgres.postgres|table` and the `d:AshPostgres.postgres|repo`. +The basic configuration for a resource requires the `d:AshPostgres.postgres|table` and the `d:AshPostgres.postgres|repo`. ```elixir # in lib/helpdesk/support/ticket.ex @@ -178,12 +193,16 @@ Now we can add the data layer to our resources. The basic configuration for a re end ``` + + #### Create the database and tables First, we'll create the database with `mix ash.setup`. Then we will generate database migrations. This is one of the many ways that AshPostgres can save time and reduce complexity. +For example: + ```bash mix ash.codegen add_tickets_and_representatives ``` @@ -200,8 +219,6 @@ Finally, we will create the local database and apply the generated migrations: mix ash.setup ``` - - ### Try it out This is based on the [Getting Started](https://hexdocs.pm/ash/getting_started.html) guide. From c46f896600f5ffb0fd003da75a7d38d705fe98c6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Sep 2024 13:13:00 -0400 Subject: [PATCH 0718/1215] fix: support pg <= 14 in resource generator, and update tests --- lib/resource_generator/spec.ex | 102 ++++++++++++++++++++++----------- test/aggregate_test.exs | 2 + test/test_helper.exs | 23 +++++++- 3 files changed, 92 insertions(+), 35 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index fffaf97e..8114a780 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -232,39 +232,75 @@ defmodule AshPostgres.ResourceGenerator.Spec do defp add_indexes(spec) do %Postgrex.Result{rows: index_rows} = - spec.repo.query!( - """ - SELECT - i.relname AS index_name, - ix.indisunique AS is_unique, - NOT(ix.indnullsnotdistinct) AS nulls_distinct, - pg_get_expr(ix.indpred, ix.indrelid) AS where_clause, - am.amname AS using_method, - idx.indexdef - FROM - pg_index ix - JOIN - pg_class i ON ix.indexrelid = i.oid - JOIN - pg_class t ON ix.indrelid = t.oid - JOIN - pg_am am ON i.relam = am.oid - LEFT JOIN - pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p' - JOIN - pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary - JOIN information_schema.tables ta - ON ta.table_name = t.relname - WHERE - t.relname = $1 - AND ta.table_schema = $2 - AND c.conindid IS NULL - GROUP BY - i.relname, ix.indisunique, ix.indnullsnotdistinct, pg_get_expr(ix.indpred, ix.indrelid), am.amname, idx.indexdef; - """, - [spec.table_name, spec.schema], - log: false - ) + if Version.match?(spec.repo.min_pg_version(), ">= 15.0") do + spec.repo.query!( + """ + SELECT + i.relname AS index_name, + ix.indisunique AS is_unique, + NOT(ix.indnullsnotdistinct) AS nulls_distinct, + pg_get_expr(ix.indpred, ix.indrelid) AS where_clause, + am.amname AS using_method, + idx.indexdef + FROM + pg_index ix + JOIN + pg_class i ON ix.indexrelid = i.oid + JOIN + pg_class t ON ix.indrelid = t.oid + JOIN + pg_am am ON i.relam = am.oid + LEFT JOIN + pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p' + JOIN + pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary + JOIN information_schema.tables ta + ON ta.table_name = t.relname + WHERE + t.relname = $1 + AND ta.table_schema = $2 + AND c.conindid IS NULL + GROUP BY + i.relname, ix.indisunique, ix.indnullsnotdistinct, pg_get_expr(ix.indpred, ix.indrelid), am.amname, idx.indexdef; + """, + [spec.table_name, spec.schema], + log: false + ) + else + spec.repo.query!( + """ + SELECT + i.relname AS index_name, + ix.indisunique AS is_unique, + TRUE AS nulls_distinct, + pg_get_expr(ix.indpred, ix.indrelid) AS where_clause, + am.amname AS using_method, + idx.indexdef + FROM + pg_index ix + JOIN + pg_class i ON ix.indexrelid = i.oid + JOIN + pg_class t ON ix.indrelid = t.oid + JOIN + pg_am am ON i.relam = am.oid + LEFT JOIN + pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p' + JOIN + pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary + JOIN information_schema.tables ta + ON ta.table_name = t.relname + WHERE + t.relname = $1 + AND ta.table_schema = $2 + AND c.conindid IS NULL + GROUP BY + i.relname, ix.indisunique, pg_get_expr(ix.indpred, ix.indrelid), am.amname, idx.indexdef; + """, + [spec.table_name, spec.schema], + log: false + ) + end %{ spec diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 630aeaac..257f2f26 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -422,6 +422,7 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + @tag :postgres_16 test "returns nil values if `include_nil?` is set to `true`" do post = Post @@ -616,6 +617,7 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + @tag :postgres_16 test "it returns `nil` values when `include_nil?` is `true`" do post = Post diff --git a/test/test_helper.exs b/test/test_helper.exs index 10ae3340..5b02474b 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,5 +1,24 @@ -# ExUnit.start(capture_log: true) -ExUnit.configure(stacktrace_depth: 100) +ExUnit.start(capture_log: true) + +exclude_tags = + case System.get_env("PG_VERSION") do + "13" -> + [:postgres_14, :postgres_15, :postgres_16] + + "14" -> + [:postgres_15, :postgres_16] + + "15" -> + [:postgres_16] + + "16" -> + [] + + _ -> + [] + end + +ExUnit.configure(stacktrace_depth: 100, exclude: exclude_tags) AshPostgres.TestRepo.start_link() AshPostgres.TestNoSandboxRepo.start_link() From 2644585104c6c70df89aecf6aa48812d659ef185 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Sep 2024 13:27:25 -0400 Subject: [PATCH 0719/1215] chore: fix version matcher --- lib/resource_generator/spec.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 8114a780..a2eaa83a 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -232,7 +232,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do defp add_indexes(spec) do %Postgrex.Result{rows: index_rows} = - if Version.match?(spec.repo.min_pg_version(), ">= 15.0") do + if Version.match?(spec.repo.min_pg_version(), ">= 15.0.0") do spec.repo.query!( """ SELECT From ea1f57ef49d4e9f418a956d08763ac39d923ac7f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 27 Sep 2024 09:01:10 -0400 Subject: [PATCH 0720/1215] chore: add tests for ash_sql fixes --- lib/data_layer.ex | 2 ++ test/calculation_test.exs | 38 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 8 +++++++ 3 files changed, 48 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 79052e73..90be9bb1 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -609,6 +609,8 @@ defmodule AshPostgres.DataLayer do def can?(_, :async_engine), do: true def can?(_, :bulk_create), do: true + def can?(_, :action_select), do: true + def can?(resource, :update_query) do # We can't currently support updating a record from a query # if that record manages a tenant on update diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 8c0f2678..fd0ab43f 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -44,6 +44,44 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + @tag :regression + test "an expression calculation that requires a left join & distinct doesn't raise errors on out of order params" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "_"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "_"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "_"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Post + |> Ash.Query.load([ + :comment_title, + :category_label, + score_plus: %{amount: 10}, + max_comment_similarity: %{to: "foobar"} + ]) + |> Ash.Query.load_calculation_as(:comment_title, {:some, :example}) + |> Ash.Query.load_calculation_as(:max_comment_similarity, {:some, :other_thing_again}, %{ + to: "foobar" + }) + |> Ash.Query.load_calculation_as(:category_label, {:some, :other_thing}) + |> Ash.Query.sort(:title) + |> Ash.read!() + end + test "an expression calculation can be filtered on" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index aadcee06..14bd29eb 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -569,6 +569,10 @@ defmodule AshPostgres.Test.Post do calculate(:has_comments, :boolean, expr(exists(comments, true == true))) + # DONT DO THIS. USE SOMETHING LIKE `first(comments.name)` + # We're doing this to test a specific breakage + calculate(:comment_title, :string, expr(comments.title)) + calculate( :has_no_followers, :boolean, @@ -635,6 +639,10 @@ defmodule AshPostgres.Test.Post do argument(:search, AshPostgres.Tsquery, allow_expr?: true, allow_nil?: false) end + calculate :score_plus, :integer, expr(score + ^arg(:amount)) do + argument(:amount, :integer, allow_nil?: false) + end + calculate :query, AshPostgres.Tsquery, expr(fragment("to_tsquery(?)", ^arg(:search))) do argument(:search, :string, allow_expr?: true, allow_nil?: false) end From 73190a3649c77864ec632feb8fddf37304cfbfcf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 27 Sep 2024 09:02:05 -0400 Subject: [PATCH 0721/1215] chore: update deps --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index d841c49e..6ce23950 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.21", "d97b060c64084613ca8317272864be908d591aaa30671d1b04de41f82f8ce368", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1fedea9b994c4b1d18722d49333fd8f30db4af058c9d56cd8cc438b420e6a6a8"}, - "ash_sql": {:hex, :ash_sql, "0.2.32", "de99255becfb9daa7991c18c870e9f276bb372acda7eda3e05c3e2ff2ca8922e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "43773bcd33d21319c11804d76fe11f1a1b7c8faba7aaedeab6f55fde3d2405db"}, + "ash_sql": {:hex, :ash_sql, "0.2.33", "bb0c53fb2920903d04a1997f6ad2efd923b90c5d47023b8c426ad79a34d4c8a0", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3dcd4ad90b7b0c73123debd59c838f7694b819723c48ed968b00f487de0ce30"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, From 085a9f74bf466ecf31eae4b9e0e8c7fb6141ab8c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 27 Sep 2024 09:02:20 -0400 Subject: [PATCH 0722/1215] chore: release version v2.4.3 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fc6628e..1579a5c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.3](https://github.com/ash-project/ash_postgres/compare/v2.4.2...v2.4.3) (2024-09-27) + + + + +### Bug Fixes: + +* support pg <= 14 in resource generator, and update tests + ## [v2.4.2](https://github.com/ash-project/ash_postgres/compare/v2.4.1...v2.4.2) (2024-09-24) diff --git a/mix.exs b/mix.exs index 49bf272f..f5899a47 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.2" + @version "2.4.3" def project do [ From a119ba29af07017d15fcd9406770c88688d78b50 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 29 Sep 2024 09:37:34 -0400 Subject: [PATCH 0723/1215] fix: handle atomic array operations --- lib/sql_implementation.ex | 8 + .../test_repo/posts/20240929121224.json | 499 ++++++++++++++++++ .../test_repo/users/20240929124728.json | 130 +++++ .../20240929121224_migrate_resources42.exs | 21 + .../20240929124728_migrate_resources43.exs | 21 + test/atomics_test.exs | 24 + test/support/resources/post.ex | 7 + test/support/resources/user.ex | 24 + 8 files changed, 734 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20240929121224.json create mode 100644 priv/resource_snapshots/test_repo/users/20240929124728.json create mode 100644 priv/test_repo/migrations/20240929121224_migrate_resources42.exs create mode 100644 priv/test_repo/migrations/20240929124728_migrate_resources43.exs diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index d01c134e..c5a13c0d 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -239,6 +239,10 @@ defmodule AshPostgres.SqlImplementation do do: nil def parameterized_type(type, constraints, no_maps?) do + if type == :array do + raise "WHAT" + end + if Ash.Type.ash_type?(type) do cast_in_query? = if function_exported?(Ash.Type, :cast_in_query?, 2) do @@ -284,6 +288,7 @@ defmodule AshPostgres.SqlImplementation do new_returns = case new_returns do {:parameterized, _} = parameterized -> parameterized + {:array, _} = type -> parameterized_type(type, []) {type, constraints} -> parameterized_type(type, constraints) other -> other end @@ -292,6 +297,9 @@ defmodule AshPostgres.SqlImplementation do {:parameterized, _} = parameterized -> parameterized + {:array, _} = type -> + parameterized_type(type, []) + {type, constraints} -> parameterized_type(type, constraints) diff --git a/priv/resource_snapshots/test_repo/posts/20240929121224.json b/priv/resource_snapshots/test_repo/posts/20240929121224.json new file mode 100644 index 00000000..02103002 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240929121224.json @@ -0,0 +1,499 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "75F7ADAAFDA2EFE1592C479941632CC8B29A1158A4A2268B9B9F5B43BD64AD7C", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/users/20240929124728.json b/priv/resource_snapshots/test_repo/users/20240929124728.json new file mode 100644 index 00000000..1a4c5eac --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20240929124728.json @@ -0,0 +1,130 @@ +{ + "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": "is_active", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "\"user\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": true, + "default": "[]", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role_list", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "users_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "name": "users_org_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_orgs" + }, + "size": null, + "source": "org_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "1259CA2EDC7624799344ADE60ACA1745688666D00D18450A22430683754B425F", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "users" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20240929121224_migrate_resources42.exs b/priv/test_repo/migrations/20240929121224_migrate_resources42.exs new file mode 100644 index 00000000..529a2338 --- /dev/null +++ b/priv/test_repo/migrations/20240929121224_migrate_resources42.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources42 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(:posts) do + add(:limited_score, :bigint) + end + end + + def down do + alter table(:posts) do + remove(:limited_score) + end + end +end diff --git a/priv/test_repo/migrations/20240929124728_migrate_resources43.exs b/priv/test_repo/migrations/20240929124728_migrate_resources43.exs new file mode 100644 index 00000000..31e815f9 --- /dev/null +++ b/priv/test_repo/migrations/20240929124728_migrate_resources43.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources43 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(:users) do + add(:role_list, {:array, :text}, default: []) + end + end + + def down do + alter table(:users) do + remove(:role_list) + end + end +end diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 2ab42b6c..2cf5e6b6 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -39,6 +39,30 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "an atomic works on a constrained integer" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) + |> Ash.create!() + + assert %{limited_score: 6} = + post + |> Ash.Changeset.for_update(:add_to_limited_score, %{amount: 6}) + |> Ash.update!() + end + + test "an atomic works on an array attribute" do + user = + User + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + assert %{role_list: [:admin]} = + user + |> Ash.Changeset.for_update(:add_role, %{role: :admin}) + |> Ash.update!() + end + test "a basic atomic works with enum/allow_nil? false" do user = User diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 14bd29eb..531fb486 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -130,6 +130,11 @@ defmodule AshPostgres.Test.Post do defaults([:read, :destroy]) + update :add_to_limited_score do + argument(:amount, :integer, allow_nil?: false) + change(atomic_update(:limited_score, expr((limited_score || 0) + ^arg(:amount)))) + end + destroy :destroy_only_freds do change(filter(expr(title == "fred"))) end @@ -359,6 +364,8 @@ defmodule AshPostgres.Test.Post do attribute(:datetime, AshPostgres.TimestamptzUsec, public?: true) attribute(:score, :integer, public?: true) + attribute(:limited_score, :integer, public?: true, constraints: [min: 0, max: 100]) + attribute(:public, :boolean, public?: true) attribute(:category, CiCategory, public?: true) attribute(:type, :atom, default: :sponsored, writable?: false, public?: false) diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index 25a99f18..b07b3e7c 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -14,6 +14,24 @@ defmodule AshPostgres.Test.User do change(atomic_update(:role, expr(invite.role))) end + update :add_role do + argument(:role, AshPostgres.Test.Role, allow_nil?: false) + + change( + atomic_update( + :role_list, + expr( + fragment( + "array(select distinct unnest(array_append(?, ?)))", + ^atomic_ref(:role_list), + ^arg(:role) + ) + ), + cast_atomic?: false + ) + ) + end + read :active do filter(expr(active)) @@ -43,6 +61,12 @@ defmodule AshPostgres.Test.User do attribute(:is_active, :boolean, public?: true) attribute(:name, :string, public?: true) attribute(:role, AshPostgres.Test.Role, allow_nil?: false, default: :user, public?: true) + + attribute(:role_list, {:array, AshPostgres.Test.Role}, + allow_nil?: false, + default: [], + public?: true + ) end postgres do From 12196cf7a11003dab1923c21dea353ca0b4e236a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 29 Sep 2024 09:39:16 -0400 Subject: [PATCH 0724/1215] chore: release version v2.4.4 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1579a5c0..cf961fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.4](https://github.com/ash-project/ash_postgres/compare/v2.4.3...v2.4.4) (2024-09-29) + + + + +### Bug Fixes: + +* handle atomic array operations + ## [v2.4.3](https://github.com/ash-project/ash_postgres/compare/v2.4.2...v2.4.3) (2024-09-27) diff --git a/mix.exs b/mix.exs index f5899a47..cbeb0eb9 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.3" + @version "2.4.4" def project do [ From af32acde466154a4e16a86e32ab183bd8ed8669b Mon Sep 17 00:00:00 2001 From: Ahmed Kamal Date: Tue, 1 Oct 2024 13:49:52 +1000 Subject: [PATCH 0725/1215] improvement: support to_ecto(%Ecto.Changeset{}) and from_ecto(%Ecto.Changeset{}) (#395) --- lib/repo.ex | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/repo.ex b/lib/repo.ex index d46107bb..b9b2290f 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -176,6 +176,10 @@ defmodule AshPostgres.Repo do Enum.map(value, &from_ecto/1) end + def from_ecto(%Ecto.Changeset{} = changeset) do + Map.put(changeset, :data, from_ecto(changeset.data)) + end + def from_ecto(%resource{} = record) do if Spark.Dsl.is?(resource, Ash.Resource) do empty = struct(resource) @@ -204,6 +208,10 @@ defmodule AshPostgres.Repo do Enum.map(value, &to_ecto/1) end + def to_ecto(%Ecto.Changeset{} = changeset) do + Map.put(changeset, :data, to_ecto(changeset.data)) + end + def to_ecto(%resource{} = record) do if Spark.Dsl.is?(resource, Ash.Resource) do resource From 02104860f38d0cc41dfbbd0dcf5f7741318f4322 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 1 Oct 2024 11:53:15 -0400 Subject: [PATCH 0726/1215] fix: ensure upsert fields are uniq --- lib/data_layer.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 90be9bb1..9607e447 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1918,7 +1918,9 @@ defmodule AshPostgres.DataLayer do upsert_fields -- Keyword.keys(Enum.at(changesets, 0).atomics) - Enum.map(fields_to_upsert, fn upsert_field -> + fields_to_upsert + |> Enum.uniq() + |> Enum.map(fn upsert_field -> # for safety, we check once more at the end that all values in # upsert_fields are names of attributes. This is because # below we use `literal/1` to bring them into the query From c9e66b15e6647acf1064a1d102c0fd0768a593fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:33:19 -0400 Subject: [PATCH 0727/1215] chore(deps): bump the production-dependencies group with 2 updates (#397) --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 6ce23950..34ad4f56 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.21", "d97b060c64084613ca8317272864be908d591aaa30671d1b04de41f82f8ce368", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1fedea9b994c4b1d18722d49333fd8f30db4af058c9d56cd8cc438b420e6a6a8"}, - "ash_sql": {:hex, :ash_sql, "0.2.33", "bb0c53fb2920903d04a1997f6ad2efd923b90c5d47023b8c426ad79a34d4c8a0", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3dcd4ad90b7b0c73123debd59c838f7694b819723c48ed968b00f487de0ce30"}, + "ash": {:hex, :ash, "3.4.22", "e292e40cae558c486bb23da656b564c3bb5fb551dbd2aeae54c879b408c91844", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6ca9be81d06ab07cb2f4d14132b085dc421f3f400b7aa0148b9fd7d575499efc"}, + "ash_sql": {:hex, :ash_sql, "0.2.34", "8b4761979284733d1c40f478af623c59bd09d67c38955f243ff88912f5c5bee8", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bc993e532aa6346231521c1c743e0660cf09344d9ffafe9e42faf59f9523743b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, @@ -19,7 +19,7 @@ "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, - "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, + "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, "igniter": {:hex, :igniter, "0.3.45", "f487138ed0c5cbf8f1bdd53360cdc2ac40c18ff379c8a575a7a34e526b4ba846", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fbe663e3f4566fb358c64bdddf0ceb05cf9175cea34cccf45a9aa5532ea967f8"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, @@ -37,7 +37,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.30", "811977b274cdad9d7668f934d44e8a013d7d9a059b63bcb6f2afb434dfcec458", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "476f2d463463ae14734f1e0c48cd642090cf3fb4082b8e73ea08118ad0da24ce"}, + "spark": {:hex, :spark, "2.2.31", "ce58988f5b34b96bb01cfc5399a5ddc24a7a5bcf0ae7003503678f3466d7779a", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1b4fdf2e0bba79a2e6417428f79dc8b7f7b01f02cdb632526071052d6cbfdfec"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 714dbf8f4d35ed882a2c0b66e541518e250e3fcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:33:57 -0400 Subject: [PATCH 0728/1215] chore(deps-dev): bump the dev-dependencies group with 2 updates (#398) --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 34ad4f56..56218cb9 100644 --- a/mix.lock +++ b/mix.lock @@ -3,11 +3,11 @@ "ash_sql": {:hex, :ash_sql, "0.2.34", "8b4761979284733d1c40f478af623c59bd09d67c38955f243ff88912f5c5bee8", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bc993e532aa6346231521c1c743e0660cf09344d9ffafe9e42faf59f9523743b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, + "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"}, "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, From 58392165a568f198b01945e2d83b99b3e9f78ca8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 4 Oct 2024 17:54:29 -0400 Subject: [PATCH 0729/1215] improvement: detect 1 arg repo use in installer --- lib/igniter.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 9b37d0d0..b005e1d5 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -108,7 +108,7 @@ defmodule AshPostgres.Igniter do end defp move_to_repo_use(zipper) do - Igniter.Code.Function.move_to_function_call(zipper, :use, 2, fn zipper -> + Igniter.Code.Function.move_to_function_call(zipper, :use, [1, 2], fn zipper -> Igniter.Code.Function.argument_equals?( zipper, 0, From 45503d22b5a94c788abd1cf603f195cebdc21902 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 6 Oct 2024 11:50:10 -0400 Subject: [PATCH 0730/1215] chore: pass `yes` option to generate a repo --- lib/igniter.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index b005e1d5..f1bbd558 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -84,7 +84,7 @@ defmodule AshPostgres.Igniter do Igniter.Code.Module.create_module( igniter, repo, - default_repo_contents(otp_app, repo), + default_repo_contents(otp_app, repo, opts), opts ) From d37ebd8f00380331f7359bcc6d79fdb0aa6cf8b1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 6 Oct 2024 11:52:32 -0400 Subject: [PATCH 0731/1215] chore: release version v2.4.5 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf961fc7..5adce0e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.5](https://github.com/ash-project/ash_postgres/compare/v2.4.4...v2.4.5) (2024-10-06) + + + + +### Bug Fixes: + +* ensure upsert fields are uniq + +### Improvements: + +* detect 1 arg repo use in installer + +* support to_ecto(%Ecto.Changeset{}) and from_ecto(%Ecto.Changeset{}) (#395) + ## [v2.4.4](https://github.com/ash-project/ash_postgres/compare/v2.4.3...v2.4.4) (2024-09-29) diff --git a/mix.exs b/mix.exs index cbeb0eb9..053b2930 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.4" + @version "2.4.5" def project do [ From 77e3dfb7ad53b597e7491525059bd1ff3d965dac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Oct 2024 11:01:22 -0400 Subject: [PATCH 0732/1215] chore: update ash_sql and tests --- mix.lock | 14 +++++++------- test/aggregate_test.exs | 26 ++++++++++++++++++++++++++ test/support/resources/post.ex | 5 +++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/mix.lock b/mix.lock index 56218cb9..d1652007 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.22", "e292e40cae558c486bb23da656b564c3bb5fb551dbd2aeae54c879b408c91844", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6ca9be81d06ab07cb2f4d14132b085dc421f3f400b7aa0148b9fd7d575499efc"}, + "ash": {:hex, :ash, "3.4.24", "a631bef823e42e204e82bf4040cf437b3c8c286a62a4913615c970e73a2fa741", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d7f10b34b298a6848eb8d2a88c4ff974a67a0d38ce3aa08caa7c8cd1bd665b"}, "ash_sql": {:hex, :ash_sql, "0.2.34", "8b4761979284733d1c40f478af623c59bd09d67c38955f243ff88912f5c5bee8", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bc993e532aa6346231521c1c743e0660cf09344d9ffafe9e42faf59f9523743b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -9,8 +9,8 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, - "ecto": {:hex, :ecto, "3.12.3", "1a9111560731f6c3606924c81c870a68a34c819f6d4f03822f370ea31a582208", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9efd91506ae722f95e48dc49e70d0cb632ede3b7a23896252a60a14ac6d59165"}, - "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, + "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, - "igniter": {:hex, :igniter, "0.3.45", "f487138ed0c5cbf8f1bdd53360cdc2ac40c18ff379c8a575a7a34e526b4ba846", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fbe663e3f4566fb358c64bdddf0ceb05cf9175cea34cccf45a9aa5532ea967f8"}, + "igniter": {:hex, :igniter, "0.3.49", "da3ce1ff42a8ba61cda462cbd46aafad9ac09be9b6fe92cbcd9a25ebf914f32f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4b2693347fdf51da6e921a5bc067fefb22698bd92c1597d06a27dc06ec686b26"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -30,18 +30,18 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"}, + "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, "reactor": {:hex, :reactor, "0.10.0", "1206113c21ba69b889e072b2c189c05a7aced523b9c3cb8dbe2dab7062cb699a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4003c33e4c8b10b38897badea395e404d74d59a31beb30469a220f2b1ffe6457"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.31", "ce58988f5b34b96bb01cfc5399a5ddc24a7a5bcf0ae7003503678f3466d7779a", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1b4fdf2e0bba79a2e6417428f79dc8b7f7b01f02cdb632526071052d6cbfdfec"}, + "spark": {:hex, :spark, "2.2.32", "cb84983c56e57670dd87a7a008a860d6e69626c814b0b5e25194b495ce56c7ba", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ee5a0a4ddb16ad8f5a792a7b1883498d3090c60101af77a866f76d54962478e8"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"}, + "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 257f2f26..bc2ac0b3 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -617,6 +617,32 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + test "it does not return `nil` values when filtered" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: nil}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "stuff"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{first_comment_nils_first_called_stuff: "stuff"} = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load([ + :first_comment_nils_first_called_stuff, + :first_comment_nils_first + ]) + |> Ash.read_one!() + end + @tag :postgres_16 test "it returns `nil` values when `include_nil?` is `true`" do post = diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 531fb486..7d2764cb 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -742,6 +742,11 @@ defmodule AshPostgres.Test.Post do sort(title: :asc_nils_first) end + first :first_comment_nils_first_called_stuff, :comments, :title do + sort(title: :asc_nils_first) + filter(expr(title == "stuff")) + end + first :first_comment_nils_first_include_nil, :comments, :title do include_nil?(true) sort(title: :asc_nils_first) From 21b923e32d902515b4418d1a139cd850de4985bc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Oct 2024 11:04:25 -0400 Subject: [PATCH 0733/1215] chore: fix deprecation warnings from igniter --- lib/data_layer.ex | 2 +- lib/igniter.ex | 8 ++++---- lib/mix/tasks/ash_postgres.install.ex | 16 ++++++++-------- lib/resource_generator/resource_generator.ex | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 9607e447..76997e3f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3014,7 +3014,7 @@ defmodule AshPostgres.DataLayer do repo = case options[:repo] do nil -> - Igniter.Code.Module.module_name(igniter, "Repo") + Igniter.Project.Module.module_name(igniter, "Repo") repo -> Igniter.Code.Module.parse(repo) diff --git a/lib/igniter.ex b/lib/igniter.ex index f1bbd558..868383bf 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -44,7 +44,7 @@ defmodule AshPostgres.Igniter do end def add_postgres_extension(igniter, repo_name, extension) do - Igniter.Code.Module.find_and_update_module!(igniter, repo_name, fn zipper -> + Igniter.Project.Module.find_and_update_module!(igniter, repo_name, fn zipper -> case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do {:ok, zipper} -> case Igniter.Code.List.append_new_to_list(zipper, extension) do @@ -77,11 +77,11 @@ defmodule AshPostgres.Igniter do case list_repos(igniter) do {igniter, []} -> if generate do - repo = Igniter.Code.Module.module_name(igniter, "Repo") + repo = Igniter.Project.Module.module_name(igniter, "Repo") otp_app = Igniter.Project.Application.app_name(igniter) igniter = - Igniter.Code.Module.create_module( + Igniter.Project.Module.create_module( igniter, repo, default_repo_contents(otp_app, repo, opts), @@ -102,7 +102,7 @@ defmodule AshPostgres.Igniter do end def list_repos(igniter) do - Igniter.Code.Module.find_all_matching_modules(igniter, fn _mod, zipper -> + Igniter.Project.Module.find_all_matching_modules(igniter, fn _mod, zipper -> move_to_repo_use(zipper) != :error end) end diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index f75a7b86..3171929a 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -26,7 +26,7 @@ defmodule Mix.Tasks.AshPostgres.Install do repo = case opts[:repo] do nil -> - Igniter.Code.Module.module_name(igniter, "Repo") + Igniter.Project.Module.module_name(igniter, "Repo") repo -> Igniter.Code.Module.parse(repo) @@ -275,7 +275,7 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp setup_data_case(igniter) do - module_name = Igniter.Code.Module.module_name(igniter, "DataCase") + module_name = Igniter.Project.Module.module_name(igniter, "DataCase") default_data_case_contents = ~s| @moduledoc """ @@ -297,24 +297,24 @@ defmodule Mix.Tasks.AshPostgres.Install do using do quote do - alias #{inspect(Igniter.Code.Module.module_name(igniter, "Repo"))} + alias #{inspect(Igniter.Project.Module.module_name(igniter, "Repo"))} import Ecto import Ecto.Changeset import Ecto.Query - import #{inspect(Igniter.Code.Module.module_name(igniter, "DataCase"))} + import #{inspect(Igniter.Project.Module.module_name(igniter, "DataCase"))} end end setup tags do - pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Code.Module.module_name(igniter, "Repo"))}, shared: not tags[:async]) + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(Igniter.Project.Module.module_name(igniter, "Repo"))}, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) :ok end | igniter - |> Igniter.Code.Module.find_and_update_or_create_module( + |> Igniter.Project.Module.find_and_update_or_create_module( module_name, default_data_case_contents, # do nothing if already exists @@ -324,7 +324,7 @@ defmodule Mix.Tasks.AshPostgres.Install do end defp setup_repo_module(igniter, otp_app, repo, opts) do - {exists?, igniter} = Igniter.Project.Module.module_exists?(igniter, repo) + {exists?, igniter} = Igniter.Project.Module.module_exists(igniter, repo) if exists? do Igniter.Project.Module.find_and_update_module!( @@ -368,7 +368,7 @@ defmodule Mix.Tasks.AshPostgres.Install do repo, &configure_installed_extensions_function/1 ) - |> Igniter.Code.Module.find_and_update_module!( + |> Igniter.Project.Module.find_and_update_module!( repo, &configure_min_pg_version_function(&1, repo, opts) ) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index d6772ba2..103305cb 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -104,7 +104,7 @@ defmodule AshPostgres.ResourceGenerator do igniter |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) - |> Igniter.Code.Module.create_module(table_spec.resource, resource) + |> Igniter.Project.Module.create_module(table_spec.resource, resource) |> then(fn igniter -> if opts[:extend] do Igniter.compose_task(igniter, "ash.patch.extend", [ From 9fe09559305db6d86ead79bad19dfdef7b35e5a4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Oct 2024 15:14:57 -0400 Subject: [PATCH 0734/1215] improvement: with `--yes` assume oldest version --- lib/igniter.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 868383bf..70d190fe 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -120,7 +120,7 @@ defmodule AshPostgres.Igniter do @doc false def get_min_pg_version(name, opts) do if opts[:yes] do - %Version{major: 16, minor: 0, patch: 0} + %Version{major: 13, minor: 0, patch: 0} else lead_in = """ Generating #{inspect(name)} From 764ecb66cdae7b34056b87f1a8073f8b23e36dda Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Oct 2024 15:16:29 -0400 Subject: [PATCH 0735/1215] chore: release version v2.4.6 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5adce0e1..1ce02035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.6](https://github.com/ash-project/ash_postgres/compare/v2.4.5...v2.4.6) (2024-10-07) + + + + +### Improvements: + +* with `--yes` assume oldest version + ## [v2.4.5](https://github.com/ash-project/ash_postgres/compare/v2.4.4...v2.4.5) (2024-10-06) diff --git a/mix.exs b/mix.exs index 053b2930..a111fc03 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.5" + @version "2.4.6" def project do [ From 53694a9e2b8e9cd30d3704294d9a0e4f0ba6da83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:55:01 -0400 Subject: [PATCH 0736/1215] chore(deps): bump the production-dependencies group with 3 updates (#401) --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index d1652007..a583ea11 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.24", "a631bef823e42e204e82bf4040cf437b3c8c286a62a4913615c970e73a2fa741", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d7f10b34b298a6848eb8d2a88c4ff974a67a0d38ce3aa08caa7c8cd1bd665b"}, - "ash_sql": {:hex, :ash_sql, "0.2.34", "8b4761979284733d1c40f478af623c59bd09d67c38955f243ff88912f5c5bee8", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bc993e532aa6346231521c1c743e0660cf09344d9ffafe9e42faf59f9523743b"}, + "ash": {:hex, :ash, "3.4.27", "8a7f8102a053c5238654a1089bbd6330f400386eb5ef5201755b6332fc4c7566", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "55d2c70f23b51bc97abb414729ced315995f502f98e8cbfad2298c2ed083b8c9"}, + "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, - "igniter": {:hex, :igniter, "0.3.49", "da3ce1ff42a8ba61cda462cbd46aafad9ac09be9b6fe92cbcd9a25ebf914f32f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4b2693347fdf51da6e921a5bc067fefb22698bd92c1597d06a27dc06ec686b26"}, + "igniter": {:hex, :igniter, "0.3.52", "b2260226c278cb11864ac2c7d171869aa5fed5a1b350ccedb7ff7020414b04f4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "98682760abca84b87588dfd578db04f575458726a3dbfef09cb2fc24c14cc4ec"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From c5c9575f3e5e344284a71a6d9d9a998a8a16ca2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:55:11 -0400 Subject: [PATCH 0737/1215] chore(deps-dev): bump git_ops in the dev-dependencies group (#402) --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index a583ea11..87d3f33b 100644 --- a/mix.lock +++ b/mix.lock @@ -18,7 +18,7 @@ "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "d571628fd829a510d219bcb7162400baff50977f", []}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, + "git_ops": {:hex, :git_ops, "2.6.2", "1e03e44d1b41e91cf39b47d214d515a0d67d44a7fda13ef131bb70bd706dd398", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fc0200fbc6d2784e2f3b9be200ce63e523d0d9ff527c6308490c3d6d325d5e6f"}, "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, "igniter": {:hex, :igniter, "0.3.52", "b2260226c278cb11864ac2c7d171869aa5fed5a1b350ccedb7ff7020414b04f4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "98682760abca84b87588dfd578db04f575458726a3dbfef09cb2fc24c14cc4ec"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, From bd91615f4913050126a4fb58cfbd8d6742010fd8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 10 Oct 2024 11:22:51 -0400 Subject: [PATCH 0738/1215] improvement: adapt to fixes and optimizations around skipped upserts in ash core test: update tests correspondingly --- lib/data_layer.ex | 12 ++++++++---- mix.exs | 2 +- mix.lock | 2 +- test/create_test.exs | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 76997e3f..34c17d9f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2588,10 +2588,14 @@ defmodule AshPostgres.DataLayer do end) |> Ash.Query.set_tenant(changeset.tenant) - with {:ok, ecto_query} <- Ash.Query.data_layer_query(ash_query), - {:ok, [result]} <- run_query(ecto_query, resource) do - {:ok, Ash.Resource.put_metadata(result, :upsert_skipped, true)} - end + {:ok, + {:upsert_skipped, ash_query, + fn -> + with {:ok, ecto_query} <- Ash.Query.data_layer_query(ash_query), + {:ok, [result]} <- run_query(ecto_query, resource) do + {:ok, Ash.Resource.put_metadata(result, :upsert_skipped, true)} + end + end}} {:ok, [result]} -> {:ok, result} diff --git a/mix.exs b/mix.exs index a111fc03..a43aed41 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.9")}, + {:ash, ash_version("~> 3.4 and >= 3.4.28")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, {:igniter, "~> 0.3 and >= 0.3.42"}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index 87d3f33b..020e7b3d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.27", "8a7f8102a053c5238654a1089bbd6330f400386eb5ef5201755b6332fc4c7566", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "55d2c70f23b51bc97abb414729ced315995f502f98e8cbfad2298c2ed083b8c9"}, "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, + "ash": {:hex, :ash, "3.4.28", "499c8cf49e406cc5a9b7c6e8e0daae616d2cbd5a02ad421db7932b2972e8badf", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7faafca1f6b70bf2e72dbabef0d667ee2108923df465878e94f03b2ea8c24fe8"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, diff --git a/test/create_test.exs b/test/create_test.exs index 23fe3533..84607880 100644 --- a/test/create_test.exs +++ b/test/create_test.exs @@ -55,7 +55,7 @@ defmodule AshPostgres.CreateTest do uniq_if_contains_foo: "foo", price: 10 }) - |> Ash.create() + |> Ash.create(return_skipped_upsert?: true) assert Ash.Resource.get_metadata(post, :upsert_skipped) end From dacc4f960e28bc02f87c0e296ff93f71a830b233 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 10 Oct 2024 11:26:31 -0400 Subject: [PATCH 0739/1215] chore: release version v2.4.7 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce02035..e20f57ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.7](https://github.com/ash-project/ash_postgres/compare/v2.4.6...v2.4.7) (2024-10-10) + + + + +### Improvements: + +* adapt to fixes and optimizations around skipped upserts in ash core + ## [v2.4.6](https://github.com/ash-project/ash_postgres/compare/v2.4.5...v2.4.6) (2024-10-07) diff --git a/mix.exs b/mix.exs index a43aed41..58c14c80 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.6" + @version "2.4.7" def project do [ From 95fe5829aab1bb9f21b807e162372da6542a4e26 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 11 Oct 2024 08:34:12 -0400 Subject: [PATCH 0740/1215] improvement: use the `name` parameter when generating migrations --- .../migration_generator.ex | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 5118b4f4..03337583 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -233,13 +233,61 @@ defmodule AshPostgres.MigrationGenerator do {"install_#{ext_name}_v#{version}_#{timestamp(true)}", "#{timestamp(true)}_install_#{ext_name}_v#{version}_extension"} - ["ash-functions" = single] -> - {"install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}_#{timestamp(true)}", - "#{timestamp(true)}_install_#{single}_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}"} - - multiple -> - {"install_#{Enum.count(multiple)}_extensions_#{timestamp(true)}", - "#{timestamp(true)}_install_#{Enum.count(multiple)}_extensions"} + ["ash_functions"] -> + {"install_ash_functions_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}_#{timestamp(true)}", + "#{timestamp(true)}_install_ash_functions_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}"} + + _multiple -> + migration_path = migration_path(opts, repo, false) + + if opts.name do + count = + migration_path + |> Path.join("*_#{opts.name}_extensions*") + |> Path.wildcard() + |> Enum.map(fn path -> + path + |> Path.basename() + |> String.split("_#{opts.name}_extensions", parts: 2) + |> Enum.at(1) + |> Integer.parse() + |> case do + {integer, _} -> + integer + + _ -> + 0 + end + end) + |> Enum.max(fn -> 0 end) + |> Kernel.+(1) + + {"#{opts.name}_extensions", "#{timestamp(true)}_#{opts.name}_extensions"} + else + count = + migration_path + |> Path.join("*_migrate_resources_extensions*") + |> Path.wildcard() + |> Enum.map(fn path -> + path + |> Path.basename() + |> String.split("_migrate_resources_extensions", parts: 2) + |> Enum.at(1) + |> Integer.parse() + |> case do + {integer, _} -> + integer + + _ -> + 0 + end + end) + |> Enum.max(fn -> 0 end) + |> Kernel.+(1) + + {"migrate_resources_extensions_#{count}", + "#{timestamp(true)}_migrate_resources_extensions_#{count}"} + end end migration_file = From 733647ac65c1e0196b86ab572a5f1d75d21d8942 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 11 Oct 2024 09:19:52 -0400 Subject: [PATCH 0741/1215] chore: remove often-confusing warning --- lib/mix/tasks/ash_postgres.generate_migrations.ex | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index eedaf280..08f46528 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -102,13 +102,6 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do domains = AshPostgres.Mix.Helpers.domains!(opts, args) - if Enum.empty?(domains) && !opts[:snapshots_only] do - IO.warn(""" - No domains found, so no resource-related migrations will be generated. - Pass the `--domains` option or configure `config :your_app, ash_domains: [...]` - """) - end - opts = opts |> Keyword.put(:format, !opts[:no_format]) From cb801701f140a90614f062cb374d56b2d0f01f80 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 11 Oct 2024 09:20:08 -0400 Subject: [PATCH 0742/1215] chore: release version v2.4.8 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e20f57ca..6bfb0a7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.8](https://github.com/ash-project/ash_postgres/compare/v2.4.7...v2.4.8) (2024-10-11) + + + + +### Improvements: + +* use the `name` parameter when generating migrations + ## [v2.4.7](https://github.com/ash-project/ash_postgres/compare/v2.4.6...v2.4.7) (2024-10-10) diff --git a/mix.exs b/mix.exs index 58c14c80..c148b7af 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.7" + @version "2.4.8" def project do [ From da61fa8535d1202cb678634487eadafa80d87ac3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 11 Oct 2024 09:20:38 -0400 Subject: [PATCH 0743/1215] chore: use `count` --- lib/migration_generator/migration_generator.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 03337583..779538e7 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -262,7 +262,8 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.max(fn -> 0 end) |> Kernel.+(1) - {"#{opts.name}_extensions", "#{timestamp(true)}_#{opts.name}_extensions"} + {"#{opts.name}_extensions_#{count}", + "#{timestamp(true)}_#{opts.name}_extensions_#{count}"} else count = migration_path From a1ffa1ca274b2598e3b04fbb6b809f9ab90f7b2c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 13 Oct 2024 18:42:11 -0400 Subject: [PATCH 0744/1215] chore: fix migration generator tests --- test/migration_generator_test.exs | 79 +++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index a5648c43..fd0e9045 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -158,7 +158,9 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(Path.wildcard("test_snapshots_path/test_repo/posts/*.json")) |> Jason.decode!(keys: :atoms!) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) file_contents = File.read!(file) @@ -264,7 +266,9 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(Path.wildcard("test_snapshots_path/test_repo/example.posts/*.json")) |> Jason.decode!(keys: :atoms!) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) file_contents = File.read!(file) @@ -334,6 +338,7 @@ defmodule AshPostgres.MigrationGeneratorTest do test "it creates multiple migration files" do assert [_, custom_index_migration] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) file = File.read!(custom_index_migration) @@ -378,6 +383,7 @@ defmodule AshPostgres.MigrationGeneratorTest do test "it adds nulls_distinct option to create index migration" do assert [custom_index_migration] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) file = File.read!(custom_index_migration) @@ -442,6 +448,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[rename table(:posts, prefix: "example"), :title, to: :name] end @@ -471,6 +478,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[rename table(:posts, prefix: "example"), :title, to: :name] assert File.read!(file2) =~ ~S[modify :title, :text, null: true, default: nil] @@ -534,6 +542,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[ALTER INDEX posts_title_index RENAME TO titles_r_unique_dawg] @@ -566,6 +575,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert [file_before, _] = String.split( @@ -601,6 +611,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[add :name, :text, null: false] @@ -627,6 +638,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[rename table(:posts), :title, to: :name] end @@ -652,6 +664,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[add :name, :text, null: false] @@ -680,6 +693,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) # Up migration assert File.read!(file2) =~ ~S[rename table(:posts), :title, to: :subject] @@ -710,6 +724,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[add :subject, :text, null: false] @@ -742,6 +757,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[add :foobar, :text] @@ -784,6 +800,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) file1_content = File.read!(file1) @@ -828,6 +845,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file2) =~ ~S[add :example, :text] <> "\n" @@ -869,7 +887,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end test "when an integer is generated and default nil, it is a bigserial" do - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S[add :id, :bigserial, null: false, primary_key: true] @@ -944,7 +964,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S[references(:posts, column: :id, name: "posts_post_id_fkey", type: :uuid, prefix: "public")] @@ -979,7 +1001,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S[references(:posts, column: :id, name: "posts_post_id_fkey", type: :text, prefix: "public")] @@ -1023,7 +1047,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S{references(:posts, column: :id, with: [related_key_id: :key_id], match: :partial, name: "posts_post_id_fkey", type: :uuid, prefix: "public")} @@ -1067,7 +1093,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S{create index(:posts, [:post_id])} end @@ -1142,7 +1170,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S{create index(:posts, [:org_id, :post_id])} end @@ -1218,7 +1248,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S{references(:users, column: :secondary_id, with: [related_key_id: :key_id, org_id: :org_id], match: :full, name: "user_things_user_id_fkey", type: :uuid, prefix: "public")} @@ -1271,7 +1303,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S{create unique_index(:users, [:name], name: "users_unique_name_index")} @@ -1337,6 +1371,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file = "test_migration_path/**/*_migrate_resources*.exs" |> Path.wildcard() + |> Enum.reject(&String.contains?(&1, "extensions")) |> Enum.sort() |> Enum.at(1) |> File.read!() @@ -1439,7 +1474,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S{references(:users, column: :secondary_id, with: [org_id: :org_id], match: :full, name: "user_things1_user_id_fkey", type: :uuid, prefix: "public")} @@ -1517,7 +1554,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S @@ -1558,6 +1597,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file = "test_migration_path/**/*_migrate_resources*.exs" |> Path.wildcard() + |> Enum.reject(&String.contains?(&1, "extensions")) |> Enum.sort() |> Enum.at(0) |> File.read!() @@ -1590,6 +1630,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file = "test_migration_path/**/*_migrate_resources*.exs" |> Path.wildcard() + |> Enum.reject(&String.contains?(&1, "extensions")) |> Enum.sort() |> Enum.at(1) |> File.read!() @@ -1643,6 +1684,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert file = "test_migration_path/**/*_migrate_resources*.exs" |> Path.wildcard() + |> Enum.reject(&String.contains?(&1, "extensions")) |> Enum.sort() |> Enum.at(1) @@ -1720,7 +1762,9 @@ defmodule AshPostgres.MigrationGeneratorTest do end test "it uses the relationship's table context if it is set" do - assert [file] = Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) assert File.read!(file) =~ ~S[references(:post_comments, column: :id, name: "posts_best_comment_id_fkey", type: :uuid, prefix: "public")] @@ -1771,7 +1815,9 @@ defmodule AshPostgres.MigrationGeneratorTest do format: false ) - assert [file1] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + assert [file1] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) file = File.read!(file1) @@ -1828,7 +1874,9 @@ defmodule AshPostgres.MigrationGeneratorTest do assert log =~ "`{\"xyz\"}`" - assert [file1] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + assert [file1] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) file = File.read!(file1) @@ -1912,6 +1960,7 @@ defmodule AshPostgres.MigrationGeneratorTest do assert [_file1, file2] = Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) file = File.read!(file2) From e05df6949ae7f40952b24661581f3568275ae7da Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 16 Oct 2024 08:50:03 -0400 Subject: [PATCH 0745/1215] chore: update tests/deps --- mix.lock | 6 +++--- test/update_test.exs | 31 +++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 020e7b3d..82b6fa4d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, "ash": {:hex, :ash, "3.4.28", "499c8cf49e406cc5a9b7c6e8e0daae616d2cbd5a02ad421db7932b2972e8badf", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7faafca1f6b70bf2e72dbabef0d667ee2108923df465878e94f03b2ea8c24fe8"}, + "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.2", "1e03e44d1b41e91cf39b47d214d515a0d67d44a7fda13ef131bb70bd706dd398", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fc0200fbc6d2784e2f3b9be200ce63e523d0d9ff527c6308490c3d6d325d5e6f"}, "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, - "igniter": {:hex, :igniter, "0.3.52", "b2260226c278cb11864ac2c7d171869aa5fed5a1b350ccedb7ff7020414b04f4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "98682760abca84b87588dfd578db04f575458726a3dbfef09cb2fc24c14cc4ec"}, + "igniter": {:hex, :igniter, "0.3.62", "254ce459354443cf2134c7dc110010b5f46f945fd976c3e529a7f0e38410b89b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f64ddad8f66feb6389b4cccde3fa971aed701127ce25aba2db43752a786d364c"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -37,7 +37,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.32", "cb84983c56e57670dd87a7a008a860d6e69626c814b0b5e25194b495ce56c7ba", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ee5a0a4ddb16ad8f5a792a7b1883498d3090c60101af77a866f76d54962478e8"}, + "spark": {:hex, :spark, "2.2.33", "221e4e3f94296e4aedfc63ba4371328f0cfcbf884c6348eaa8ed636003d0c5c2", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "663bc54574c55d6bfaf40eaa2732a197b42715020cca73a5b1737a4b398997d3"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/update_test.exs b/test/update_test.exs index e67db4ae..4a3ad58c 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -18,8 +18,7 @@ defmodule AshPostgres.UpdateTest do chat_history: [ %{"content" => "Default system prompt", "role" => "system"}, %{ - "content" => - "Here's a collection of tweets from the Twitter list 'Web 3 Mid':\nTweet by KhanAbbas201 (2024-08-26 19:40:13.000000Z):\n@Rahatcodes told me I look good wearing the Eth shirt. https://t.co/xBugAt2tDi\nTweet by Rahatcodes (2024-08-26 19:42:55.000000Z):\n@KhanAbbas201 I dont recall saying this\nTweet by KhanAbbas201 (2024-08-26 19:44:08.000000Z):\n@Rahatcodes Damn what happened to your memory bruv?\nTweet by angelinarusse (2024-08-26 19:56:05.000000Z):\n@dabit3 Real degens call it Twitter\nTweet by angelinarusse (2024-08-26 20:13:58.000000Z):\n@hamseth They tried the same in Afghanistan and it didn’t go well for them.\nTweet by KhanAbbas201 (2024-08-26 20:39:53.000000Z):\nTweet by Osh_mahajan (2024-08-26 21:34:08.000000Z):\n@FedericoNoemie 🐾🐾🦘\nTweet by developer_dao (2024-08-26 21:39:59.000000Z):\n@ZwigoZwitscher @ArweaveEco @k4yls Wildly high praise, ty ty. @k4yls is 🔥 with a 🐶\nTweet by developer_dao (2024-08-26 21:41:17.000000Z):\nRT @ZwigoZwitscher : I've been into @ArweaveEco for years – as an interested outsider – and still learned new things in that course 👇. Thanks…\nTweet by angelin...eet by developer_dao (2024-08-26 22:18:09.000000Z):\nRT @jeremykauffman: BREAKING: France has arrested Gonzalve Bich, the CEO of Bic\nTweet by PatrickAlphaC (2024-08-26 22:26:16.000000Z):\n@oxfav @ar_io_network\n", + "content" => "stuff", "role" => "user" }, %{"content" => "test", "role" => "user"}, @@ -60,4 +59,32 @@ defmodule AshPostgres.UpdateTest do ) |> Ash.update!() end + + test "can unrelate belongs_to" do + author = + AshPostgres.Test.Author + |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"}) + |> Ash.create!() + + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + assert is_nil(post.author) == false + + post = + post + |> Ash.Changeset.for_update(:update) + |> Ash.Changeset.manage_relationship(:author, author, type: :remove) + |> Ash.update!() + + post + |> Ash.load!(:author) + |> Map.get(:author) + |> IO.inspect() + + assert is_nil(post.author) + end end From bdee388d4c2696d92598c282db92c3fbe460ce04 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 16 Oct 2024 08:55:17 -0400 Subject: [PATCH 0746/1215] fix: fix resource generator task & tests --- lib/mix/tasks/ash_postgres.gen.resources.ex | 11 +++++++++++ lib/resource_generator/resource_generator.ex | 2 +- test/resource_generator_test.exs | 4 +++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 097a45fe..9cc885ff 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -76,6 +76,17 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do Mix.Project.config()[:app] |> Application.get_env(:ecto_repos, []) + repos = + repos + |> List.wrap() + |> Enum.map(fn v -> + if is_binary(v) do + Igniter.Code.Module.parse(v) + else + v + end + end) + case repos do [] -> igniter diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 103305cb..a4fbdeb4 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -106,7 +106,7 @@ defmodule AshPostgres.ResourceGenerator do |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) |> Igniter.Project.Module.create_module(table_spec.resource, resource) |> then(fn igniter -> - if opts[:extend] do + if opts[:extend] && opts[:extend] != [] do Igniter.compose_task(igniter, "ash.patch.extend", [ table_spec.resource | opts[:extend] || [] ]) diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index e2b655ed..30a14a1a 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -22,7 +22,9 @@ defmodule AshPostgres.ResourceGeenratorTests do "MyApp.Accounts", "--tables", "example_table", - "--yes" + "--yes", + "--repo", + "AshPostgres.TestRepo" ]) |> assert_creates("lib/my_app/accounts/example_table.ex", """ defmodule MyApp.Accounts.ExampleTable do From 2ea4a0d6c720775c2a71679de81b0ce22a41f9fd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 16 Oct 2024 08:55:44 -0400 Subject: [PATCH 0747/1215] chore: release version v2.4.9 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bfb0a7a..01c0576a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline +## [v2.4.9](https://github.com/ash-project/ash_postgres/compare/v2.4.8...v2.4.9) (2024-10-16) + + + + +### Bug Fixes: + +* fix resource generator task & tests + ## [v2.4.8](https://github.com/ash-project/ash_postgres/compare/v2.4.7...v2.4.8) (2024-10-11) diff --git a/mix.exs b/mix.exs index c148b7af..2558ae19 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.8" + @version "2.4.9" def project do [ From af64faa82d81628f479650dbf0df7ef8afc5683e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 16 Oct 2024 08:56:19 -0400 Subject: [PATCH 0748/1215] chore: remove IO.inspect --- test/update_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/update_test.exs b/test/update_test.exs index 4a3ad58c..953a55d9 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -83,7 +83,6 @@ defmodule AshPostgres.UpdateTest do post |> Ash.load!(:author) |> Map.get(:author) - |> IO.inspect() assert is_nil(post.author) end From 971295921295c53c17f0bf9ac8a266299a231e3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:09:17 -0400 Subject: [PATCH 0749/1215] chore(deps): bump the production-dependencies group with 2 updates (#404) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.28 to 3.4.32 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.28...v3.4.32) Updates `igniter` from 0.3.62 to 0.3.63 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.62...v0.3.63) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 82b6fa4d..b499d628 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.28", "499c8cf49e406cc5a9b7c6e8e0daae616d2cbd5a02ad421db7932b2972e8badf", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7faafca1f6b70bf2e72dbabef0d667ee2108923df465878e94f03b2ea8c24fe8"}, + "ash": {:hex, :ash, "3.4.32", "7445f6876491acecf1621ad97434540a360f82fbfeb7c8c4f7971c92c9ae5f91", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a20224455a842f792d3aeb1fe313dedf619e4ad3d60a583f863b7417f8dc1991"}, "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.2", "1e03e44d1b41e91cf39b47d214d515a0d67d44a7fda13ef131bb70bd706dd398", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fc0200fbc6d2784e2f3b9be200ce63e523d0d9ff527c6308490c3d6d325d5e6f"}, "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, - "igniter": {:hex, :igniter, "0.3.62", "254ce459354443cf2134c7dc110010b5f46f945fd976c3e529a7f0e38410b89b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f64ddad8f66feb6389b4cccde3fa971aed701127ce25aba2db43752a786d364c"}, + "igniter": {:hex, :igniter, "0.3.63", "ac27c466e6f779cf5f39d200a7d433cd91c8d465277e001661dc9b4680ca9eb3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5e24f71479cfd3575f79a767db51de0b38a633f05107b05d94ef1a54fde9093f"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -37,7 +37,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.33", "221e4e3f94296e4aedfc63ba4371328f0cfcbf884c6348eaa8ed636003d0c5c2", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "663bc54574c55d6bfaf40eaa2732a197b42715020cca73a5b1737a4b398997d3"}, + "spark": {:hex, :spark, "2.2.34", "1f0a3bd86d37f86a1d26db4a34d6b0e5fb091940aee25cd40041dab1397c8ada", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "93f94b8f511a72f8764465ea32ff2e5376695f70e747884de2ce64bb6ac22a59"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From c4cc32961612adc9546e8c185f369c572fd9388f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:09:40 -0400 Subject: [PATCH 0750/1215] chore(deps-dev): bump git_ops in the dev-dependencies group (#405) Bumps the dev-dependencies group with 1 update: [git_ops](https://github.com/zachdaniel/git_ops). Updates `git_ops` from 2.6.2 to 2.6.3 - [Changelog](https://github.com/zachdaniel/git_ops/blob/master/CHANGELOG.md) - [Commits](https://github.com/zachdaniel/git_ops/commits/v2.6.3) --- updated-dependencies: - dependency-name: git_ops dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index b499d628..814808be 100644 --- a/mix.lock +++ b/mix.lock @@ -18,7 +18,7 @@ "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "d571628fd829a510d219bcb7162400baff50977f", []}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.6.2", "1e03e44d1b41e91cf39b47d214d515a0d67d44a7fda13ef131bb70bd706dd398", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fc0200fbc6d2784e2f3b9be200ce63e523d0d9ff527c6308490c3d6d325d5e6f"}, + "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, "igniter": {:hex, :igniter, "0.3.63", "ac27c466e6f779cf5f39d200a7d433cd91c8d465277e001661dc9b4680ca9eb3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5e24f71479cfd3575f79a767db51de0b38a633f05107b05d94ef1a54fde9093f"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, From a9f1a00316def8e2f30ca652a5a074c29ddf7d91 Mon Sep 17 00:00:00 2001 From: skrioify Date: Fri, 18 Oct 2024 13:04:00 +0200 Subject: [PATCH 0751/1215] fix: race condition compiling migrations when concurrently creating new tenants (#406) --- lib/migration_compile_cache.ex | 38 ++++++++++++++++++++++++++++++++++ lib/multitenancy.ex | 7 ++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 lib/migration_compile_cache.ex diff --git a/lib/migration_compile_cache.ex b/lib/migration_compile_cache.ex new file mode 100644 index 00000000..7c79dcc2 --- /dev/null +++ b/lib/migration_compile_cache.ex @@ -0,0 +1,38 @@ +defmodule AshPostgres.MigrationCompileCache do + @moduledoc """ + A cache for the compiled migrations. + + This is used to avoid recompiling the migration files + every time a migration is run, as well as ensuring that + migrations are compiled sequentially. + + This is important because otherwise there is a race condition + where two invocations could be compiling the same migration at + once, which would error out. + """ + + def start_link(opts \\ %{}) do + Agent.start_link(fn -> opts end, name: __MODULE__) + end + + @doc """ + Compile a file, caching the result for future calls. + """ + def compile_file(file) do + Agent.get_and_update(__MODULE__, fn state -> + new_state = ensure_compiled(state, file) + {Map.get(new_state, file), new_state} + end) + end + + defp ensure_compiled(state, file) do + case Map.get(state, file) do + nil -> + compiled = Code.compile_file(file) + Map.put(state, file, compiled) + _ -> + state + end + end + +end diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index ad4bbfbb..2f7fd0db 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -61,7 +61,7 @@ defmodule AshPostgres.MultiTenancy do end defp load_migration!({version, _, file}) when is_binary(file) do - loaded_modules = file |> Code.compile_file() |> Enum.map(&elem(&1, 0)) + loaded_modules = file |> compile_file() |> Enum.map(&elem(&1, 0)) if mod = Enum.find(loaded_modules, &migration?/1) do {version, mod} @@ -70,6 +70,11 @@ defmodule AshPostgres.MultiTenancy do "file #{Path.relative_to_cwd(file)} does not define an Ecto.Migration" end end + + defp compile_file(file) do + AshPostgres.MigrationCompileCache.start_link() + AshPostgres.MigrationCompileCache.compile_file(file) + end defp migration?(mod) do function_exported?(mod, :__migration__, 0) From e3b4e04769594ef57e2742b4123a1f03b0e2a169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Almir=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Oct 2024 17:38:50 +0200 Subject: [PATCH 0752/1215] docs: Fix links in documentation (#408) --- CHANGELOG.md | 24 +++++++++---------- README.md | 2 +- .../topics/advanced/manual-relationships.md | 2 +- .../get-started-with-ash-postgres.md | 2 +- lib/functions/trigram_similarity.ex | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c0576a..ae5bfd73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -See [Conventional Commits](Https://conventionalcommits.org) for commit guidelines. +See [Conventional Commits](https://www.conventionalcommits.org) for commit guidelines. @@ -205,13 +205,13 @@ causing nontrivial performance issues at scale. - [`ash_postgres.gen.migration`] dynamically select and allow setting a repo -## [v2.1.17](https://github.com/ash-project/ash_postgres/compare/v2.1.16...v2.1.17) (2024-07-27) +## v2.1.17 (2024-07-27) ### Improvements: - [`ash_sql`] update ash & ash_sql for various fixes -## [v2.1.16](https://github.com/ash-project/ash_postgres/compare/v2.1.15...v2.1.16) (2024-07-25) +## v2.1.16 (2024-07-25) ### Bug Fixes: @@ -273,7 +273,7 @@ causing nontrivial performance issues at scale. - [`mix ash.gen.resource`] pluralize table name in extender -## [v2.1.8](https://github.com/ash-project/ash_postgres/compare/v2.1.7...v2.1.8) (2024-07-17) +## v2.1.8 (2024-07-17) ### Bug Fixes: @@ -287,7 +287,7 @@ causing nontrivial performance issues at scale. - [expressions] add `binding()` expression, for referring to the current table -## [v2.1.7](https://github.com/ash-project/ash_postgres/compare/v2.1.6...v2.1.7) (2024-07-17) +## v2.1.7 (2024-07-17) ### Bug Fixes: @@ -351,13 +351,13 @@ causing nontrivial performance issues at scale. - [query builder] update ash & improve type casting behavior -## [v2.1.1](https://github.com/ash-project/ash_postgres/compare/v2.1.0...v2.1.1) (2024-07-10) +## v2.1.1 (2024-07-10) ### Bug Fixes: - [mix ash_postgres.install] properly interpolate module names in installer -## [v2.1.0](https://github.com/ash-project/ash_postgres/compare/v2.0.12...v2.1.0) (2024-07-10) +## v2.1.0 (2024-07-10) ### Features: @@ -432,7 +432,7 @@ causing nontrivial performance issues at scale. ## [v2.0.8](https://github.com/ash-project/ash_postgres/compare/v2.0.7...v2.0.8) (2024-06-06) -## [v2.0.7](https://github.com/ash-project/ash_postgres/compare/v2.0.6...v2.0.7) (2024-06-06) +## v2.0.7 (2024-06-06) ### Bug Fixes: @@ -440,7 +440,7 @@ causing nontrivial performance issues at scale. - [fix] ensure that all current attribute values are selected on bulk update shifted root query -## [v2.0.6](https://github.com/ash-project/ash_postgres/compare/v2.0.5...v2.0.6) (2024-05-29) +## v2.0.6 (2024-05-29) ### Bug Fixes: @@ -458,7 +458,7 @@ causing nontrivial performance issues at scale. - [mix ash_postgres.squash_snapshots] add `ash_postgres.squash_snapshots` mix task (#302) -## [v2.0.5](https://github.com/ash-project/ash_postgres/compare/v2.0.4...v2.0.5) (2024-05-24) +## v2.0.5 (2024-05-24) ### Improvements: @@ -498,13 +498,13 @@ causing nontrivial performance issues at scale. - [AshPostgres.MigrationGenerator] properly parse previous version from migration generation -## [v2.0.1](https://github.com/ash-project/ash_postgres/compare/v2.0.0...v2.0.1) (2024-05-12) +## v2.0.1 (2024-05-12) ### Bug Fixes: - [AshPostgres.MigrationGenerator] properly parse previous version of custom extensions when generating migrations -## [v2.0.0](https://github.com/ash-project/ash_postgres/compare/v2.0.0...2.0) +## v2.0.0 The changelog is starting over. Please see `/documentation/1.0-CHANGELOG.md` in GitHub for previous changelogs. diff --git a/README.md b/README.md index 2d9bb79d..2e019bde 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-white-text.png?raw=true#gh-dark-mode-only) ![Elixir CI](https://github.com/ash-project/ash_postgres/workflows/CI/badge.svg) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/license/MIT) [![Hex version badge](https://img.shields.io/hexpm/v/ash_postgres.svg)](https://hex.pm/packages/ash_postgres) [![Hexdocs badge](https://img.shields.io/badge/docs-hexdocs-purple)](https://hexdocs.pm/ash_postgres) diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index ef261c5d..f3b57df4 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -1,6 +1,6 @@ # Manual Relationships -See [Defining Manual Relationships](https://hexdocs.pm/ash/defining-manual-relationships.html) for an idea of manual relationships in general. +See [Manual Relationships](https://hexdocs.pm/ash/relationships.html#manual-relationships) for an idea of manual relationships in general. Manual relationships allow for expressing complex/non-typical relationships between resources in a standard way. Individual data layers may interact with manual relationships in their own way, so see their corresponding guides. diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index 4ad0c498..12d88328 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -221,7 +221,7 @@ mix ash.setup ### Try it out -This is based on the [Getting Started](https://hexdocs.pm/ash/getting_started.html) guide. +This is based on the [Get Started](https://hexdocs.pm/ash/get-started.html) guide. If you haven't already, you should read that first. And now we're ready to try it out! Run the following in iex: diff --git a/lib/functions/trigram_similarity.ex b/lib/functions/trigram_similarity.ex index 7987f430..97177ae6 100644 --- a/lib/functions/trigram_similarity.ex +++ b/lib/functions/trigram_similarity.ex @@ -2,7 +2,7 @@ defmodule AshPostgres.Functions.TrigramSimilarity do @moduledoc """ Maps to the builtin postgres trigram similarity function. Requires `pgtrgm` extension to be installed. - See the postgres docs on [trigram](https://www.postgresql.org/docs/9.6/pgtrgm.html]) for more information. + See the postgres docs on [trigram](https://www.postgresql.org/docs/9.6/pgtrgm.html) for more information. Requires the pg_trgm extension. Configure which extensions you have installed in your `AshPostgres.Repo` From d6999d4e80cf0c93607fa62f1eb5fec1ba374dc5 Mon Sep 17 00:00:00 2001 From: Ethan Dang Date: Sun, 20 Oct 2024 16:39:43 +0700 Subject: [PATCH 0753/1215] doc: Fix migration path for tenant migration (#409) --- .../topics/development/migrations-and-tasks.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 619dded2..a9d6b951 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -68,13 +68,8 @@ Tasks that need to be executed in the released application (because mix is not p load_app() for repo <- repos() do - repo_name = repo |> Module.split() |> List.last() |> Macro.underscore() - - path = - "priv/" - |> Path.join(repo_name) - |> Path.join("tenant_migrations") - # This may be different for you if you are not using the default tenant migrations + path = Ecto.Migrator.migrations_path(repo, "tenant_migrations") + # This may be different for you if you are not using the default tenant migrations {:ok, _, _} = Ecto.Migrator.with_repo( @@ -103,13 +98,9 @@ Tasks that need to be executed in the released application (because mix is not p # only needed if you are using postgres multitenancy def rollback_tenants(repo, version) do load_app() - repo_name = repo |> Module.split() |> List.last() |> Macro.underscore() - path = - "priv/" - |> Path.join(repo_name) - |> Path.join("tenant_migrations") - # This may be different for you if you are not using the default tenant migrations + path = Ecto.Migrator.migrations_path(repo, "tenant_migrations") + # This may be different for you if you are not using the default tenant migrations for tenant <- repo.all_tenants() do {:ok, _, _} = From 1228fcd851f29a68609e236f7d6a2622a4b5c4ba Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Oct 2024 22:11:13 -0400 Subject: [PATCH 0754/1215] fix: run any query that could produce errors when performing atomic upgrade --- lib/data_layer.ex | 12 +++++++++++- lib/mix/tasks/ash_postgres.gen.resources.ex | 4 ++-- lib/mix/tasks/ash_postgres.install.ex | 2 +- mix.lock | 8 ++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 34c17d9f..06c05037 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1358,6 +1358,16 @@ defmodule AshPostgres.DataLayer do :empty -> if options[:return_records?] do if changeset.context[:data_layer][:use_atomic_update_data?] do + case query.__ash_bindings__ do + %{expression_accumulator: %AshSql.Expr.ExprInfo{has_error?: true}} -> + # if the query could produce an error + # we must run it even if we will just be returning the original data. + repo.all(query) + + _ -> + :ok + end + {:ok, [changeset.data]} else {:ok, repo.all(query)} @@ -3021,7 +3031,7 @@ defmodule AshPostgres.DataLayer do Igniter.Project.Module.module_name(igniter, "Repo") repo -> - Igniter.Code.Module.parse(repo) + Igniter.Project.Module.parse(repo) end igniter diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 9cc885ff..6816117a 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -67,7 +67,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do {%{domain: domain}, argv} = positional_args!(argv) - domain = Igniter.Code.Module.parse(domain) + domain = Igniter.Project.Module.parse(domain) options = options!(argv) @@ -81,7 +81,7 @@ defmodule Mix.Tasks.AshPostgres.Gen.Resources do |> List.wrap() |> Enum.map(fn v -> if is_binary(v) do - Igniter.Code.Module.parse(v) + Igniter.Project.Module.parse(v) else v end diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 3171929a..280e7f20 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -29,7 +29,7 @@ defmodule Mix.Tasks.AshPostgres.Install do Igniter.Project.Module.module_name(igniter, "Repo") repo -> - Igniter.Code.Module.parse(repo) + Igniter.Project.Module.parse(repo) end otp_app = Igniter.Project.Application.app_name(igniter) diff --git a/mix.lock b/mix.lock index 814808be..ec6f2170 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.32", "7445f6876491acecf1621ad97434540a360f82fbfeb7c8c4f7971c92c9ae5f91", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a20224455a842f792d3aeb1fe313dedf619e4ad3d60a583f863b7417f8dc1991"}, + "ash": {:hex, :ash, "3.4.35", "0e27cd577809e4fda01df86c0e48e93ba6d02618b6b173b1d182d557f693358c", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77b091ba74a7a788348716f1dcaa350c18439b2d714926a6f8a5c394c2015751"}, "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -19,8 +19,8 @@ "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, - "glob_ex": {:hex, :glob_ex, "0.1.9", "b97a25392f5339e49f587e5b24c468c6a4f38299febd5ec85c5f8bb2e42b5c1e", [:mix], [], "hexpm", "be72e584ad1d8776a4d134d4b6da1bac8b80b515cdadf0120e0920b9978d7f01"}, - "igniter": {:hex, :igniter, "0.3.63", "ac27c466e6f779cf5f39d200a7d433cd91c8d465277e001661dc9b4680ca9eb3", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5e24f71479cfd3575f79a767db51de0b38a633f05107b05d94ef1a54fde9093f"}, + "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, + "igniter": {:hex, :igniter, "0.3.72", "f7c4ddeb369c5cba2725ac28b20a00202186415d0176f7727422d98d093e7004", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "2fbe74116ed9e539901f0255cb9ebffdc11411620851d2a933e3ee18c68df62c"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -37,7 +37,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, - "spark": {:hex, :spark, "2.2.34", "1f0a3bd86d37f86a1d26db4a34d6b0e5fb091940aee25cd40041dab1397c8ada", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "93f94b8f511a72f8764465ea32ff2e5376695f70e747884de2ce64bb6ac22a59"}, + "spark": {:hex, :spark, "2.2.35", "1c0bb30f340151eca24164885935de39e6ada4010555f444c813d0488990f8f3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f242d6385c287389034a0e146d8f025b5c9ab777f1ae5cf0fdfc9209db6ae748"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 0570dccd5fd183eb24dfe21e6cb24402da018ac9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Oct 2024 22:18:00 -0400 Subject: [PATCH 0755/1215] fix: when an atomic update is fully skipped, run the query if it could produce errors --- README.md | 2 +- ...-AshPostgres.DataLayer.md => DSL-AshPostgres.DataLayer.md} | 0 lib/migration_compile_cache.ex | 4 ++-- lib/multitenancy.ex | 2 +- mix.exs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename documentation/dsls/{DSL:-AshPostgres.DataLayer.md => DSL-AshPostgres.DataLayer.md} (100%) diff --git a/README.md b/README.md index 2e019bde..da0136ae 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,4 @@ Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https:// ## Reference -- [AshPostgres.DataLayer DSL](documentation/dsls/DSL:-AshPostgres.DataLayer.md) +- [AshPostgres.DataLayer DSL](documentation/dsls/DSL-AshPostgres.DataLayer.md) diff --git a/documentation/dsls/DSL:-AshPostgres.DataLayer.md b/documentation/dsls/DSL-AshPostgres.DataLayer.md similarity index 100% rename from documentation/dsls/DSL:-AshPostgres.DataLayer.md rename to documentation/dsls/DSL-AshPostgres.DataLayer.md diff --git a/lib/migration_compile_cache.ex b/lib/migration_compile_cache.ex index 7c79dcc2..c8f9108a 100644 --- a/lib/migration_compile_cache.ex +++ b/lib/migration_compile_cache.ex @@ -19,7 +19,7 @@ defmodule AshPostgres.MigrationCompileCache do Compile a file, caching the result for future calls. """ def compile_file(file) do - Agent.get_and_update(__MODULE__, fn state -> + Agent.get_and_update(__MODULE__, fn state -> new_state = ensure_compiled(state, file) {Map.get(new_state, file), new_state} end) @@ -30,9 +30,9 @@ defmodule AshPostgres.MigrationCompileCache do nil -> compiled = Code.compile_file(file) Map.put(state, file, compiled) + _ -> state end end - end diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 2f7fd0db..fbae77fe 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -70,7 +70,7 @@ defmodule AshPostgres.MultiTenancy do "file #{Path.relative_to_cwd(file)} does not define an Ecto.Migration" end end - + defp compile_file(file) do AshPostgres.MigrationCompileCache.start_link() AshPostgres.MigrationCompileCache.compile_file(file) diff --git a/mix.exs b/mix.exs index 2558ae19..9826789a 100644 --- a/mix.exs +++ b/mix.exs @@ -97,7 +97,7 @@ defmodule AshPostgres.MixProject do "documentation/topics/advanced/expressions.md", "documentation/topics/advanced/schema-based-multitenancy.md", "documentation/topics/advanced/manual-relationships.md", - "documentation/dsls/DSL:-AshPostgres.DataLayer.md", + "documentation/dsls/DSL-AshPostgres.DataLayer.md", "CHANGELOG.md" ], groups_for_extras: [ From fe62a09d7bd77f84ada3edd2e8978432ee0bbebb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Oct 2024 22:19:13 -0400 Subject: [PATCH 0756/1215] chore: release version v2.4.10 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae5bfd73..17d611ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.10](https://github.com/ash-project/ash_postgres/compare/v2.4.9...v2.4.10) (2024-10-23) + + + + +### Bug Fixes: + +* when an atomic update is fully skipped, run the query if it could produce errors + +* run any query that could produce errors when performing atomic upgrade + +* race condition compiling migrations when concurrently creating new tenants (#406) + ## [v2.4.9](https://github.com/ash-project/ash_postgres/compare/v2.4.8...v2.4.9) (2024-10-16) diff --git a/mix.exs b/mix.exs index 9826789a..54405e26 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.9" + @version "2.4.10" def project do [ From c7aa5f6a1b583436bb671b4064e5759cf57ada21 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 23 Oct 2024 09:19:40 -0400 Subject: [PATCH 0757/1215] fix: ensure repo_opts is passed through to `repo.all/2` --- lib/data_layer.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 06c05037..5c7acead 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1362,7 +1362,7 @@ defmodule AshPostgres.DataLayer do %{expression_accumulator: %AshSql.Expr.ExprInfo{has_error?: true}} -> # if the query could produce an error # we must run it even if we will just be returning the original data. - repo.all(query) + repo.all(query, repo_opts) _ -> :ok @@ -1370,7 +1370,7 @@ defmodule AshPostgres.DataLayer do {:ok, [changeset.data]} else - {:ok, repo.all(query)} + {:ok, repo.all(query, repo_opts)} end else :ok From b49bc8f3aebb4223f5a2a076a5f2d397e0dc3801 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 23 Oct 2024 09:19:59 -0400 Subject: [PATCH 0758/1215] chore: release version v2.4.11 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d611ac..ec27bf6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.11](https://github.com/ash-project/ash_postgres/compare/v2.4.10...v2.4.11) (2024-10-23) + + + + +### Bug Fixes: + +* ensure repo_opts is passed through to `repo.all/2` + ## [v2.4.10](https://github.com/ash-project/ash_postgres/compare/v2.4.9...v2.4.10) (2024-10-23) diff --git a/mix.exs b/mix.exs index 54405e26..fed5bde9 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.10" + @version "2.4.11" def project do [ From 48a80297601416c2f0988203819e97ae1a5a3c2f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 23 Oct 2024 09:22:55 -0400 Subject: [PATCH 0759/1215] Create SECURITY.md --- SECURITY.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..87d03b8b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +This is a copy of the security policy in the core Ash repo. That is the authoritative source. + +[![OpenSSF Vulnerability Disclosure](https://img.shields.io/badge/OpenSSF-Vulnerability_Disclosure-green)](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md) +[![GitHub Report](https://img.shields.io/badge/GitHub-Security_Advisories-blue)](https://github.com/ash-project/ash/security/advisories/new) +[![Email Report](https://img.shields.io/badge/Email-security%40ash--hq.org-blue)](mailto:security@ash-hq.org) + +This repository follows the [OpenSSF Vulnerability Disclosure guide](https://github.com/ossf/oss-vulnerability-guide/tree/main). You can learn more about it in the [Finders Guide](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md). + +Please report vulnerabilities via the [GitHub Security Vulnerability Reporting](https://github.com/ash-project/ash/security/advisories/new) +or via email to [`security@ash-hq.org`](mailto:security@ash-hq.org) if this does not work for you. + +Someone from the core team respond within 3 working days of your +report. If the issue is confirmed as a vulnerability, we will open a Security +Advisory. This project follows a 90 day disclosure timeline. + +If you have questions about reporting security issues, email the vulnerability +management team: [`security@erlef.org`](mailto:security@erlef.org) From 802d5b2b2769f062ac003414ee4b49ebb463f581 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 23 Oct 2024 11:42:23 -0400 Subject: [PATCH 0760/1215] test: add test confirming fix for GHSA-hf59-7rwq-785m docs: illustrate where the fix occurred in the changelog --- CHANGELOG.md | 79 ++++++------------- ...ic_non_bulk_actions_policy_bypass_test.exs | 27 +++++++ test/support/domain.ex | 1 + .../resources/post_with_empty_update.ex | 41 ++++++++++ 4 files changed, 94 insertions(+), 54 deletions(-) create mode 100644 test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs create mode 100644 test/support/resources/post_with_empty_update.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index ec27bf6a..bbd0d4d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,130 +7,101 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide ## [v2.4.11](https://github.com/ash-project/ash_postgres/compare/v2.4.10...v2.4.11) (2024-10-23) - - - ### Bug Fixes: -* ensure repo_opts is passed through to `repo.all/2` +- ensure repo_opts is passed through to `repo.all/2` ## [v2.4.10](https://github.com/ash-project/ash_postgres/compare/v2.4.9...v2.4.10) (2024-10-23) +## Security - +- Patch of [GHSA-hf59-7rwq-785m](https://github.com/ash-project/ash_postgres/security/advisories/GHSA-hf59-7rwq-785m) Empty, atomic, non-bulk actions, policy bypass for side-effects vulnerability. ### Bug Fixes: -* when an atomic update is fully skipped, run the query if it could produce errors +- when an atomic update is fully skipped, run the query if it could produce errors -* run any query that could produce errors when performing atomic upgrade +- run any query that could produce errors when performing atomic upgrade -* race condition compiling migrations when concurrently creating new tenants (#406) +- race condition compiling migrations when concurrently creating new tenants (#406) ## [v2.4.9](https://github.com/ash-project/ash_postgres/compare/v2.4.8...v2.4.9) (2024-10-16) - - - ### Bug Fixes: -* fix resource generator task & tests +- fix resource generator task & tests ## [v2.4.8](https://github.com/ash-project/ash_postgres/compare/v2.4.7...v2.4.8) (2024-10-11) - - - ### Improvements: -* use the `name` parameter when generating migrations +- use the `name` parameter when generating migrations ## [v2.4.7](https://github.com/ash-project/ash_postgres/compare/v2.4.6...v2.4.7) (2024-10-10) - - - ### Improvements: -* adapt to fixes and optimizations around skipped upserts in ash core +- adapt to fixes and optimizations around skipped upserts in ash core ## [v2.4.6](https://github.com/ash-project/ash_postgres/compare/v2.4.5...v2.4.6) (2024-10-07) - - - ### Improvements: -* with `--yes` assume oldest version +- with `--yes` assume oldest version ## [v2.4.5](https://github.com/ash-project/ash_postgres/compare/v2.4.4...v2.4.5) (2024-10-06) - - - ### Bug Fixes: -* ensure upsert fields are uniq +- ensure upsert fields are uniq ### Improvements: -* detect 1 arg repo use in installer +- detect 1 arg repo use in installer -* support to_ecto(%Ecto.Changeset{}) and from_ecto(%Ecto.Changeset{}) (#395) +- support to_ecto(%Ecto.Changeset{}) and from_ecto(%Ecto.Changeset{}) (#395) ## [v2.4.4](https://github.com/ash-project/ash_postgres/compare/v2.4.3...v2.4.4) (2024-09-29) - - - ### Bug Fixes: -* handle atomic array operations +- handle atomic array operations ## [v2.4.3](https://github.com/ash-project/ash_postgres/compare/v2.4.2...v2.4.3) (2024-09-27) - - - ### Bug Fixes: -* support pg <= 14 in resource generator, and update tests +- support pg <= 14 in resource generator, and update tests ## [v2.4.2](https://github.com/ash-project/ash_postgres/compare/v2.4.1...v2.4.2) (2024-09-24) - - - ### Bug Fixes: -* typo of `biging` -> `bigint` +- typo of `biging` -> `bigint` -* altering attributes not properly generating foreign keys in some cases +- altering attributes not properly generating foreign keys in some cases -* installer: use correct module name in the `DataCase` moduledocs. (#393) +- installer: use correct module name in the `DataCase` moduledocs. (#393) -* trim input before passing to `String.to_integer/1`. (#389) +- trim input before passing to `String.to_integer/1`. (#389) ### Improvements: -* add `--repo` option to installer, and warn on clashing existing repo +- add `--repo` option to installer, and warn on clashing existing repo -* prompt for minimum pg version +- prompt for minimum pg version -* adjust mix task aliases to be used with `ash_postgres` +- adjust mix task aliases to be used with `ash_postgres` -* set a name for generated migrations +- set a name for generated migrations ## [v2.4.1](https://github.com/ash-project/ash_postgres/compare/v2.4.0...v2.4.1) (2024-09-16) - - - ### Bug Fixes: -* ensure that returning is not an empty list +- ensure that returning is not an empty list -* match on table schema as well as table name +- match on table schema as well as table name ## [v2.4.0](https://github.com/ash-project/ash_postgres/compare/v2.3.1...v2.4.0) (2024-09-13) diff --git a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs new file mode 100644 index 00000000..5863f499 --- /dev/null +++ b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs @@ -0,0 +1,27 @@ +defmodule AshPostgres.EmptyAtomicNonBulkActionsPolicyBypassTest do + @moduledoc """ + This is test verifies the fix for the following CVE: + + https://github.com/ash-project/ash_postgres/security/advisories/GHSA-hf59-7rwq-785m + """ + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.PostWithEmptyUpdate + + require Ash.Query + + test "a forbidden error is appropriately raised on atomic upgraded, empty, non-bulk actions" do + post = + PostWithEmptyUpdate + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + Logger.configure(level: :debug) + + assert_raise Ash.Error.Forbidden, fn -> + post + |> Ash.Changeset.for_update(:empty_update, %{}, authorize?: true) + |> Ash.update!() + end + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index 353ca4d6..a571efb9 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -22,6 +22,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) resource(AshPostgres.Test.StatefulPostFollower) + resource(AshPostgres.Test.PostWithEmptyUpdate) end authorization do diff --git a/test/support/resources/post_with_empty_update.ex b/test/support/resources/post_with_empty_update.ex new file mode 100644 index 00000000..fcc1bace --- /dev/null +++ b/test/support/resources/post_with_empty_update.ex @@ -0,0 +1,41 @@ +defmodule AshPostgres.Test.PostWithEmptyUpdate do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [ + Ash.Policy.Authorizer + ] + + require Ash.Sort + + policies do + policy action(:empty_update) do + # force visiting the database + authorize_if(expr(fragment("TRUE = FALSE"))) + end + end + + postgres do + table("posts") + repo(AshPostgres.TestRepo) + migrate? false + end + + actions do + defaults([:create, :read]) + + update :empty_update do + accept([]) + end + end + + attributes do + uuid_primary_key(:id, writable?: true) + + attribute(:title, :string) do + public?(true) + source(:title_column) + end + end +end From 4b939b5ec8a3fe65ccbb08ab17d3f65e281a2c58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 08:52:22 -0400 Subject: [PATCH 0761/1215] chore(deps): bump the production-dependencies group with 3 updates (#410) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [igniter](https://github.com/ash-project/igniter) and [postgrex](https://github.com/elixir-ecto/postgrex). Updates `ash` from 3.4.35 to 3.4.36 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.35...v3.4.36) Updates `igniter` from 0.3.72 to 0.3.73 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.3.72...v0.3.73) Updates `postgrex` from 0.19.1 to 0.19.2 - [Release notes](https://github.com/elixir-ecto/postgrex/releases) - [Changelog](https://github.com/elixir-ecto/postgrex/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/postgrex/compare/v0.19.1...v0.19.2) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: postgrex dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index ec6f2170..d4f89a6c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.35", "0e27cd577809e4fda01df86c0e48e93ba6d02618b6b173b1d182d557f693358c", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77b091ba74a7a788348716f1dcaa350c18439b2d714926a6f8a5c394c2015751"}, + "ash": {:hex, :ash, "3.4.36", "a5987bbdc86e8e7dbe28cf470ad0a33d6240869e842101471da2ffb7b84c8a27", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22075b11e1873c4a7cc8548bc477187143bbae3cbcc1e890bfc3aecf4ca493a4"}, "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, - "igniter": {:hex, :igniter, "0.3.72", "f7c4ddeb369c5cba2725ac28b20a00202186415d0176f7727422d98d093e7004", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "2fbe74116ed9e539901f0255cb9ebffdc11411620851d2a933e3ee18c68df62c"}, + "igniter": {:hex, :igniter, "0.3.73", "4ce625f586fc793067fd2bb7c96a422833fb5b5a6a4f836372054ac607cdc858", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "304baa473923f27834c8c7542e624341d2baac66ba5c5d26b57239ee9bc54032"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -31,7 +31,7 @@ "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, - "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, + "postgrex": {:hex, :postgrex, "0.19.2", "34d6884a332c7bf1e367fc8b9a849d23b43f7da5c6e263def92784d03f9da468", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "618988886ab7ae8561ebed9a3c7469034bf6a88b8995785a3378746a4b9835ec"}, "reactor": {:hex, :reactor, "0.10.0", "1206113c21ba69b889e072b2c189c05a7aced523b9c3cb8dbe2dab7062cb699a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4003c33e4c8b10b38897badea395e404d74d59a31beb30469a220f2b1ffe6457"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, From aff40b05178e1ee88c37732d0662250ed7e05e1e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 26 Oct 2024 09:59:14 -0400 Subject: [PATCH 0762/1215] docs: add example of recursive relationship --- .../topics/advanced/manual-relationships.md | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index f3b57df4..47e90e54 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -85,3 +85,187 @@ defmodule Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold do end end ``` + +## Recursive Relationships + +Manual relationships can be _very_ powerful, as they can leverage the full power of Ecto to do arbitrarily complex things. +Here is an example of a recursive relationship that loads all employees under the purview of a given manager using a recursive CTE. + +> ### Use ltree {: .info} +> +> While the below is very powerful, if at all possible we suggest using ltree for hierarchical data. Its built in to postgres +> and AshPostgres has built in support for it. For more, see: `AshPostgres.Ltree`. + +Keep in mind this is an example of a very advanced use case, _not_ something you'd typically need to do. + +```elixir +defmodule MyApp.Employee.ManagedEmployees do + @moduledoc """ + A manual relationship which uses a recursive CTE to find all employees managed by a given employee. + """ + + use Ash.Resource.ManualRelationship + use AshPostgres.ManualRelationship + alias MyApp.Employee + alias MyApp.Repo + import Ecto.Query + + @doc false + @impl true + @spec load([Employee.t()], keyword, map) :: + {:ok, %{Ash.UUID.t() => [Employee.t()]}} | {:error, any} + def load(employees, _opts, _context) do + employee_ids = Enum.map(employees, & &1.id) + + all_descendants = + Employee + |> where([l], l.manager_id in ^employee_ids) + |> recursive_cte_query("employee_tree", Employee) + |> Repo.all() + + employees + |> with_descendants(all_descendants) + |> Map.new(&{&1.id, &1.descendants}) + |> then(&{:ok, &1}) + end + + defp with_descendants([], _), do: [] + + defp with_descendants(employees, all_descendants) do + Enum.map(employees, fn employee -> + descendants = Map.get(all_descendants, employee.id, []) + + %{employee | descendants: with_descendants(descendants, all_descendants)} + end) + end + + @doc false + @impl true + @spec ash_postgres_join( + Ecto.Query.t(), + opts :: keyword, + current_binding :: any, + as_binding :: any, + :inner | :left, + Ecto.Query.t() + ) :: + {:ok, Ecto.Query.t()} | {:error, any} + # Add a join from some binding in the query, producing *as_binding*. + def ash_postgres_join(query, _opts, current_binding, as_binding, join_type, destination_query) do + immediate_parents = + from(destination in destination_query, + where: parent_as(^current_binding).manager_id == destination.id + ) + + cte_name = "employees_#{as_binding}" + + descendant_query = + recursive_cte_query_for_join( + immediate_parents, + cte_name, + destination_query + ) + + case join_type do + :inner -> + {:ok, + from(row in query, + inner_lateral_join: descendant in subquery(descendant_query), + on: true, + as: ^as_binding + )} + + :left -> + {:ok, + from(row in query, + left_lateral_join: descendant in subquery(descendant_query), + on: true, + as: ^as_binding + )} + end + end + + @impl true + @spec ash_postgres_subquery(keyword, any, any, Ecto.Query.t()) :: + {:ok, Ecto.Query.t()} | {:error, any} + # Produce a subquery using which will use the given binding and will be + def ash_postgres_subquery(_opts, current_binding, as_binding, destination_query) do + immediate_descendants = + from(destination in Employee, + where: parent_as(^current_binding).id == destination.manager_id + ) + + cte_name = "employees_#{as_binding}" + + recursive_cte_query = + recursive_cte_query_for_join( + immediate_descendants, + cte_name, + Employee + ) + + other_query = + from(row in subquery(recursive_cte_query), + where: + row.id in subquery( + from(row in Ecto.Query.exclude(destination_query, :select), select: row.id) + ) + ) + + {:ok, other_query} + end + + defp recursive_cte_query(immediate_parents, cte_name, query) do + recursion_query = + query + |> join(:inner, [l], lt in ^cte_name, on: l.manager_id == lt.id) + + descendants_query = + immediate_parents + |> union(^recursion_query) + + {cte_name, Employee} + |> recursive_ctes(true) + |> with_cte(^cte_name, as: ^descendants_query) + end + + defp recursive_cte_query_for_join(immediate_parents, cte_name, query) do + # This is due to limitations in ecto's recursive CTE implementation + # For more, see here: + # https://elixirforum.com/t/ecto-cte-queries-without-a-prefix/33148/2 + # https://stackoverflow.com/questions/39458572/ecto-declare-schema-for-a-query + employee_keys = Employee.__schema__(:fields) + + cte_name = + from(cte in fragment("?", literal(^cte_name)), select: map(cte, ^employee_keys)) + + recursion_query = + query + |> join(:inner, [l], lt in ^cte_name, on: l.manager_id == lt.id) + + descendants_query = + immediate_parents + |> union(^recursion_query) + + cte_name + |> recursive_ctes(true) + |> with_cte(^cte_name, as: ^descendants_query) + end +end +``` + +With the above definition, employees could have a relationship like this: + +```elixir +has_many :managed_employees, MyApp.Employee do + manual MyApp.Employee.ManagedEmployees +end +``` + +And you could then use it in calculations and aggregates! For example, to see the count of employees managed by each employee: + +```elixir +aggregates do + count :count_of_managed_employees, :managed_employees +end +``` From 879c8f1e27e39b8899475d7d4dfcb811eae6b873 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 26 Oct 2024 10:14:26 -0400 Subject: [PATCH 0763/1215] fix: don't use `cast` for changes --- lib/data_layer.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5c7acead..1941398a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2078,7 +2078,8 @@ defmodule AshPostgres.DataLayer do record |> to_ecto() |> set_table(changeset, type, table_error?) - |> Ecto.Changeset.change(Map.take(changeset.attributes, attributes_to_change)) + |> Ecto.Changeset.cast(%{}, []) + |> force_changes(Map.take(changeset.attributes, attributes_to_change)) |> add_configured_foreign_key_constraints(record.__struct__) |> add_unique_indexes(record.__struct__, changeset) |> add_check_constraints(record.__struct__) @@ -2100,6 +2101,12 @@ defmodule AshPostgres.DataLayer do end end + defp force_changes(changeset, changes) do + Enum.reduce(changes, changeset, fn {key, value}, changeset -> + Ecto.Changeset.force_change(changeset, key, value) + end) + end + defp handle_raised_error( %Ecto.StaleEntryError{changeset: %{data: %resource{}, filters: filters}}, stacktrace, From d861ce0c7720b0edc1e10110d929f3dc396d0689 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 27 Oct 2024 14:59:21 -0400 Subject: [PATCH 0764/1215] improvement: support prefer_transaction? --- lib/data_layer.ex | 22 +++++++++++++++---- lib/repo.ex | 7 ++++++ ...ic_non_bulk_actions_policy_bypass_test.exs | 2 -- test/support/test_repo.ex | 2 ++ test/test_helper.exs | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1941398a..a16267ea 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -605,6 +605,11 @@ defmodule AshPostgres.DataLayer do import Ecto.Query, only: [from: 2, subquery: 1] + @impl true + def prefer_transaction?(resource) do + AshPostgres.DataLayer.Info.repo(resource, :mutate).prefer_transaction?() + end + @impl true def can?(_, :async_engine), do: true def can?(_, :bulk_create), do: true @@ -1773,7 +1778,12 @@ defmodule AshPostgres.DataLayer do |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation) upsert_set = - upsert_set(resource, changesets, options) + upsert_set( + resource, + changesets, + options[:upsert_keys] || Ash.Resource.Info.primary_key(resource), + options + ) on_conflict = case AshSql.Atomics.query_with_atomics( @@ -1785,7 +1795,11 @@ defmodule AshPostgres.DataLayer do upsert_set ) do :empty -> - {:replace, options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)} + if options[:return_records?] do + {:replace, options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)} + else + :nothing + end {:ok, query} -> query @@ -1913,7 +1927,7 @@ defmodule AshPostgres.DataLayer do fun.() end - defp upsert_set(resource, changesets, options) do + defp upsert_set(resource, changesets, keys, options) do attributes_changing_anywhere = changesets |> Enum.flat_map(&Map.keys(&1.attributes)) |> Enum.uniq() @@ -1926,7 +1940,7 @@ defmodule AshPostgres.DataLayer do fields_to_upsert = upsert_fields -- - Keyword.keys(Enum.at(changesets, 0).atomics) + Keyword.keys(Enum.at(changesets, 0).atomics) -- keys fields_to_upsert |> Enum.uniq() diff --git a/lib/repo.ex b/lib/repo.ex index b9b2290f..38c33601 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -69,6 +69,9 @@ defmodule AshPostgres.Repo do @doc "The default prefix(postgres schema) to use when building queries" @callback default_prefix() :: String.t() + @doc "Whether or not to explicitly start and close a transaction for each action, even if there are no transaction hooks" + @callback prefer_transaction?() :: boolean + @doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields" @callback override_migration_type(atom) :: atom @doc "Should the repo should be created by `mix ash_postgres.create`?" @@ -102,6 +105,9 @@ defmodule AshPostgres.Repo do def create?, do: true def drop?, do: true + # default to false in 4.0 + def prefer_transaction?, do: true + def transaction!(fun) do case fun.() do {:ok, value} -> value @@ -242,6 +248,7 @@ defmodule AshPostgres.Repo do on_transaction_begin: 1, installed_extensions: 0, all_tenants: 0, + prefer_transaction?: 0, tenant_migrations_path: 0, default_prefix: 0, override_migration_type: 1, diff --git a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs index 5863f499..8b204f25 100644 --- a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs +++ b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs @@ -16,8 +16,6 @@ defmodule AshPostgres.EmptyAtomicNonBulkActionsPolicyBypassTest do |> Ash.Changeset.for_create(:create, %{}) |> Ash.create!() - Logger.configure(level: :debug) - assert_raise Ash.Error.Forbidden, fn -> post |> Ash.Changeset.for_update(:empty_update, %{}, authorize?: true) diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index c272a2e5..999e928e 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -7,6 +7,8 @@ defmodule AshPostgres.TestRepo do send(self(), data) end + def prefer_transaction?, do: false + def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- Application.get_env(:ash_postgres, :no_extensions, []) diff --git a/test/test_helper.exs b/test/test_helper.exs index 5b02474b..23858cfd 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.start(capture_log: true) +ExUnit.start(capture_log: false) exclude_tags = case System.get_env("PG_VERSION") do From 60ce92433804a792337e4552dd03613f884a702e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 27 Oct 2024 19:40:45 -0400 Subject: [PATCH 0765/1215] improvement: add prefer_transaction_for_atomic_updates?/1 --- lib/data_layer.ex | 17 +++++++++--- lib/repo.ex | 14 ++++++++-- test/atomics_test.exs | 54 +++++++++++++++++++-------------------- test/support/test_repo.ex | 4 ++- test/test_helper.exs | 2 +- test/upsert_test.exs | 32 +++++++++++++++++++++++ 6 files changed, 88 insertions(+), 35 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a16267ea..002f4c0c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -610,6 +610,11 @@ defmodule AshPostgres.DataLayer do AshPostgres.DataLayer.Info.repo(resource, :mutate).prefer_transaction?() end + @impl true + def prefer_transaction_for_atomic_updates?(resource) do + AshPostgres.DataLayer.Info.repo(resource, :mutate).prefer_transaction_for_atomic_updates?() + end + @impl true def can?(_, :async_engine), do: true def can?(_, :bulk_create), do: true @@ -1515,22 +1520,25 @@ defmodule AshPostgres.DataLayer do query.limit || query.offset -> with {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do - {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} + {:ok, from(row in Ecto.Query.subquery(root_query), []), + root_query.__ash_bindings__.expression_accumulator, atomics != []} end !Enum.empty?(query.joins) || has_exists? -> with root_query <- Ecto.Query.exclude(root_query, :order_by), {:ok, root_query} <- AshSql.Atomics.select_atomics(resource, root_query, atomics) do - {:ok, from(row in Ecto.Query.subquery(root_query), []), atomics != []} + {:ok, from(row in Ecto.Query.subquery(root_query), []), + root_query.__ash_bindings__.expression_accumulator, atomics != []} end true -> - {:ok, Ecto.Query.exclude(root_query, :order_by), false} + {:ok, Ecto.Query.exclude(root_query, :order_by), + root_query.__ash_bindings__.expression_accumulator, false} end case root_query_result do - {:ok, root_query, selected_atomics?} -> + {:ok, root_query, acc, selected_atomics?} -> dynamic = Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn pkey, dynamic -> if dynamic do @@ -1557,6 +1565,7 @@ defmodule AshPostgres.DataLayer do AshPostgres.SqlImplementation, context ) + |> AshSql.Bindings.merge_expr_accumulator(acc) |> then(fn query -> if selected_atomics? do Map.update!(query, :__ash_bindings__, &Map.put(&1, :atomics_in_binding, 0)) diff --git a/lib/repo.ex b/lib/repo.ex index 38c33601..5cc66e8d 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -69,9 +69,12 @@ defmodule AshPostgres.Repo do @doc "The default prefix(postgres schema) to use when building queries" @callback default_prefix() :: String.t() - @doc "Whether or not to explicitly start and close a transaction for each action, even if there are no transaction hooks" + @doc "Whether or not to explicitly start and close a transaction for each action, even if there are no transaction hooks. Defaults to `true`." @callback prefer_transaction?() :: boolean + @doc "Whether or not to explicitly start and close a transaction for each atomic update action, even if there are no transaction hooks. Defaults to `false`." + @callback prefer_transaction_for_atomic_updates?() :: boolean + @doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields" @callback override_migration_type(atom) :: atom @doc "Should the repo should be created by `mix ash_postgres.create`?" @@ -95,7 +98,11 @@ defmodule AshPostgres.Repo do @before_compile AshPostgres.Repo.BeforeCompile require Logger - defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1 + defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1, transaction: 1, transaction: 2 + + def transaction(fun, opts \\ []) do + super(fun, opts) + end def installed_extensions, do: [] def tenant_migrations_path, do: nil @@ -108,6 +115,8 @@ defmodule AshPostgres.Repo do # default to false in 4.0 def prefer_transaction?, do: true + def prefer_transaction_for_atomic_updates?, do: false + def transaction!(fun) do case fun.() do {:ok, value} -> value @@ -249,6 +258,7 @@ defmodule AshPostgres.Repo do installed_extensions: 0, all_tenants: 0, prefer_transaction?: 0, + prefer_transaction_for_atomic_updates?: 0, tenant_migrations_path: 0, default_prefix: 0, override_migration_type: 1, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 2cf5e6b6..2b77815a 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -341,10 +341,10 @@ defmodule AshPostgres.AtomicsTest do Enum.each( [ - :exists, - :list, - :count, - :combined + :exists + # :list, + # :count, + # :combined ], fn aggregate -> test "can use #{aggregate} in validation" do @@ -365,29 +365,29 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end - assert_raise Ash.Error.Invalid, ~r/Can only update if Post has no comments/, fn -> - post - |> Ash.Changeset.new() - |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) - |> Ash.Changeset.for_update(:update_if_no_comments_non_atomic, %{title: "bar"}) - |> Ash.update!() - end - - assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> - post - |> Ash.Changeset.new() - |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) - |> Ash.Changeset.for_destroy(:destroy_if_no_comments_non_atomic, %{}) - |> Ash.destroy!() - end - - assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> - post - |> Ash.Changeset.new() - |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) - |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) - |> Ash.destroy!() - end + # assert_raise Ash.Error.Invalid, ~r/Can only update if Post has no comments/, fn -> + # post + # |> Ash.Changeset.new() + # |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + # |> Ash.Changeset.for_update(:update_if_no_comments_non_atomic, %{title: "bar"}) + # |> Ash.update!() + # end + + # assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + # post + # |> Ash.Changeset.new() + # |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + # |> Ash.Changeset.for_destroy(:destroy_if_no_comments_non_atomic, %{}) + # |> Ash.destroy!() + # end + + # assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + # post + # |> Ash.Changeset.new() + # |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + # |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) + # |> Ash.destroy!() + # end end end ) diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 999e928e..4b9319d1 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -7,7 +7,9 @@ defmodule AshPostgres.TestRepo do send(self(), data) end - def prefer_transaction?, do: false + def prefer_transaction?, do: true + + def prefer_transaction_for_atomic_updates?, do: false def installed_extensions do ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- diff --git a/test/test_helper.exs b/test/test_helper.exs index 23858cfd..5b02474b 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.start(capture_log: false) +ExUnit.start(capture_log: true) exclude_tags = case System.get_env("PG_VERSION") do diff --git a/test/upsert_test.exs b/test/upsert_test.exs index ca29bad6..08058701 100644 --- a/test/upsert_test.exs +++ b/test/upsert_test.exs @@ -4,6 +4,38 @@ defmodule AshPostgres.Test.UpsertTest do require Ash.Query + test "empty upserts" do + id = Ash.UUID.generate() + + new_post = + Post + |> Ash.Changeset.for_create(:create, %{ + id: id, + title: "title2" + }) + |> Ash.create!() + + assert new_post.id == id + assert new_post.created_at == new_post.updated_at + + updated_post = + Post + |> Ash.Changeset.for_create( + :create, + %{ + id: id, + title: "title2" + }, + upsert?: true, + upsert_fields: [], + return_skipped_upsert?: true + ) + |> Ash.create!() + + assert updated_post.id == id + assert updated_post.updated_at == new_post.updated_at + end + test "upserting results in the same created_at timestamp, but a new updated_at timestamp" do id = Ash.UUID.generate() From 474d80460b2758cb98d04a1c079127db35a15447 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 27 Oct 2024 19:50:18 -0400 Subject: [PATCH 0766/1215] improvement: set `prefer_transaction?` to false in generated repos --- lib/igniter.ex | 6 ++++++ lib/mix/tasks/ash_postgres.install.ex | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/igniter.ex b/lib/igniter.ex index 70d190fe..e7f782ad 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -12,6 +12,12 @@ defmodule AshPostgres.Igniter do %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} end + # Don't open unnecessary transactions + # will default to `false` in 4.0 + def prefer_transaction? do + false + end + def installed_extensions do # Add extensions here, and the migration generator will install them. ["ash-functions"] diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 280e7f20..12c79266 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -368,6 +368,10 @@ defmodule Mix.Tasks.AshPostgres.Install do repo, &configure_installed_extensions_function/1 ) + |> Igniter.Project.Module.find_and_update_module!( + repo, + &configure_prefer_transaction_function/1 + ) |> Igniter.Project.Module.find_and_update_module!( repo, &configure_min_pg_version_function(&1, repo, opts) @@ -443,6 +447,23 @@ defmodule Mix.Tasks.AshPostgres.Install do end end + defp configure_prefer_transaction_function(zipper) do + case Igniter.Code.Function.move_to_def(zipper, :prefer_transaction?, 0) do + {:ok, zipper} -> + {:ok, zipper} + + _ -> + {:ok, + Igniter.Code.Common.add_code(zipper, """ + # Don't open unnecessary transactions + # will default to `false` in 4.0 + def prefer_transaction? do + false + end + """)} + end + end + defp configure_min_pg_version_function(zipper, repo, opts) do case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do {:ok, zipper} -> From e94aeab1774f0da93e4557fbdfe4d2429c022a92 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 28 Oct 2024 11:30:02 -0400 Subject: [PATCH 0767/1215] fix: don't double add distinct clauses --- lib/data_layer.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 002f4c0c..1efc3caa 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1097,6 +1097,8 @@ defmodule AshPostgres.DataLayer do where: ^source_filter ) + data_layer_query = Ecto.Query.exclude(data_layer_query, :distinct) + if query.__ash_bindings__[:__order__?] do {:ok, from(source in data_layer_query, @@ -1186,6 +1188,8 @@ defmodule AshPostgres.DataLayer do ) ) + data_layer_query = Ecto.Query.exclude(data_layer_query, :distinct) + {:ok, from(source in data_layer_query, where: field(source, ^source_attribute) in ^source_values, @@ -1224,6 +1228,8 @@ defmodule AshPostgres.DataLayer do ) ) + data_layer_query = Ecto.Query.exclude(data_layer_query, :distinct) + {:ok, from(source in data_layer_query, where: field(source, ^source_attribute) in ^source_values, From 62d1556864c3583eba0a21f12e8bc8dd3b706fbf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 28 Oct 2024 11:35:03 -0400 Subject: [PATCH 0768/1215] chore: update ash_sql --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index fed5bde9..b0c4e7dc 100644 --- a/mix.exs +++ b/mix.exs @@ -165,7 +165,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.28")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.30")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.37")}, {:igniter, "~> 0.3 and >= 0.3.42"}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index d4f89a6c..bc49041d 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, - "igniter": {:hex, :igniter, "0.3.73", "4ce625f586fc793067fd2bb7c96a422833fb5b5a6a4f836372054ac607cdc858", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "304baa473923f27834c8c7542e624341d2baac66ba5c5d26b57239ee9bc54032"}, + "igniter": {:hex, :igniter, "0.3.75", "fa8b655d6c8b34c4c8c223214c1429f4a61cc6faeb337e4320b39cc064522eb6", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ce0b97ee4fb93302bc90029253343834d86b1db5ba33fbab46dbd1c7827197ea"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 0e336d56e1091beec15d6f9173f5067e0a0f9271 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 28 Oct 2024 11:59:11 -0400 Subject: [PATCH 0769/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index bc49041d..4c8d4fc1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.36", "a5987bbdc86e8e7dbe28cf470ad0a33d6240869e842101471da2ffb7b84c8a27", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22075b11e1873c4a7cc8548bc477187143bbae3cbcc1e890bfc3aecf4ca493a4"}, - "ash_sql": {:hex, :ash_sql, "0.2.36", "e5722123de5b726ad3185ef8c8ce5ef17b78d3409e822cadeadc6ba5110601fe", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a95b5ebccfe5e74d7fc4e46b104abae4d1003b53cbc8418fcb5fa3c6e0c081a9"}, + "ash_sql": {:hex, :ash_sql, "0.2.37", "ec29820a501a9e7af57bae182741d13d9a71742019d71a5bfeac819346de0186", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "abd87869b8ebbd999491b947bb2f22c2e01e67fd3aa9b0fec68ab4f624e1da24"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, From 59fc4c4cfb11922fec4760395d0dfd99313d4f5d Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:43:47 +0800 Subject: [PATCH 0770/1215] chore: Tidy up wording and capitalization of installation messages (#414) --- lib/igniter.ex | 6 +++--- lib/mix/tasks/ash_postgres.install.ex | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index e7f782ad..e4ddd6fb 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -131,10 +131,10 @@ defmodule AshPostgres.Igniter do lead_in = """ Generating #{inspect(name)} - What is the minimum postgres version you will be using? + What is the minimum PostgreSQL version you will be using? - AshPostgres uses this information when generating queries and migrations - to choose the best available features for your version of postgres. + AshPostgres uses this information when generating queries and migrations, + to choose the best available features for your version of PostgreSQL. """ format_request = diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 12c79266..51af5713 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -351,7 +351,7 @@ defmodule Mix.Tasks.AshPostgres.Install do """ Repo module #{inspect(repo)} existed, but was not an `Ecto.Repo` or an `AshPostgres.Repo`. - Please rerun the ash_postgresql installer with the `--repo` option to specify a repo. + Please re-run the AshPostgres installer with the `--repo` option to specify a repo. """} end end From 5a9b34394108c8aa3e016c3a03b14a4413523e76 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 29 Oct 2024 21:42:36 -0400 Subject: [PATCH 0771/1215] improvement: update ash --- mix.exs | 2 +- mix.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index b0c4e7dc..cdf67ee7 100644 --- a/mix.exs +++ b/mix.exs @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.28")}, + {:ash, ash_version("~> 3.4 and >= 3.4.37")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.37")}, {:igniter, "~> 0.3 and >= 0.3.42"}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index 4c8d4fc1..49f4391e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.36", "a5987bbdc86e8e7dbe28cf470ad0a33d6240869e842101471da2ffb7b84c8a27", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22075b11e1873c4a7cc8548bc477187143bbae3cbcc1e890bfc3aecf4ca493a4"}, + "ash": {:hex, :ash, "3.4.37", "30be35acd32cd5995d08a30cda4b0c7cc33f8c343911e6115131ec858b5fd01a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2.5", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d8281fea518299ee165ff2704de0c769d838937b48258d1a94a3fc8000d32490"}, "ash_sql": {:hex, :ash_sql, "0.2.37", "ec29820a501a9e7af57bae182741d13d9a71742019d71a5bfeac819346de0186", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "abd87869b8ebbd999491b947bb2f22c2e01e67fd3aa9b0fec68ab4f624e1da24"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, - "igniter": {:hex, :igniter, "0.3.75", "fa8b655d6c8b34c4c8c223214c1429f4a61cc6faeb337e4320b39cc064522eb6", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ce0b97ee4fb93302bc90029253343834d86b1db5ba33fbab46dbd1c7827197ea"}, + "igniter": {:hex, :igniter, "0.3.76", "ff283416402f4d1ef3f79ab57d38aac08389b3768fc81da03795ce5347f1167f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4692f874f969dc4856167469a3415a5a57a362314f37e1cdb14431316a74e896"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -36,10 +36,10 @@ "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, + "sourceror": {:hex, :sourceror, "1.7.0", "62c34f4e3a109d837edd652730219b6332745e0ec7db34d5d350a5cdf30b376a", [:mix], [], "hexpm", "3dd2b1bd780fd0df48089f48480a54fd065bf815b63ef8046219d7784e7435f3"}, "spark": {:hex, :spark, "2.2.35", "1c0bb30f340151eca24164885935de39e6ada4010555f444c813d0488990f8f3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f242d6385c287389034a0e146d8f025b5c9ab777f1ae5cf0fdfc9209db6ae748"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, - "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, + "splode": {:hex, :splode, "0.2.5", "3e6fab3efb7340c5a67f906a69f8048db900f39a429c9275d9edb0a993b8be4d", [:mix], [], "hexpm", "ce95e724a1f468b547e6825f825267c6a0af13dcad0bfc23b10e1c7982a92f09"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, From cd04474a926ecddeb7ebe036459736ff698923bc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 29 Oct 2024 21:49:07 -0400 Subject: [PATCH 0772/1215] chore: release version v2.4.12 --- CHANGELOG.md | 72 +++++++++++++++++++++++++++++----------------------- mix.exs | 2 +- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd0d4d5..68267468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,25 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.12](https://github.com/ash-project/ash_postgres/compare/v2.4.11...v2.4.12) (2024-10-30) + +### Bug Fixes: + +* [query builder] don't double add distinct clauses + +* [`AshPostgres.DataLayer`] don't use `cast` for changes + +### Improvements: + +* [`AshPostgres.Repo`] set `prefer_transaction?` to false in generated repos + +* [`AshPostgres.DataLayer`] support prefer_transaction? + ## [v2.4.11](https://github.com/ash-project/ash_postgres/compare/v2.4.10...v2.4.11) (2024-10-23) ### Bug Fixes: -- ensure repo_opts is passed through to `repo.all/2` +- [upserts] ensure repo_opts is passed through to `repo.all/2` ## [v2.4.10](https://github.com/ash-project/ash_postgres/compare/v2.4.9...v2.4.10) (2024-10-23) @@ -19,107 +33,101 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide ### Bug Fixes: -- when an atomic update is fully skipped, run the query if it could produce errors +- [upserts] run any query that could produce errors when performing atomic upgrade -- run any query that could produce errors when performing atomic upgrade - -- race condition compiling migrations when concurrently creating new tenants (#406) +- [multitenant migrations] race condition compiling migrations when concurrently creating new tenants (#406) ## [v2.4.9](https://github.com/ash-project/ash_postgres/compare/v2.4.8...v2.4.9) (2024-10-16) ### Bug Fixes: -- fix resource generator task & tests +- [`mix ash_postgres.gen.resources`] fix resource generator task & tests ## [v2.4.8](https://github.com/ash-project/ash_postgres/compare/v2.4.7...v2.4.8) (2024-10-11) ### Improvements: -- use the `name` parameter when generating migrations +- [migration generator] use the `name` parameter when generating migrations ## [v2.4.7](https://github.com/ash-project/ash_postgres/compare/v2.4.6...v2.4.7) (2024-10-10) ### Improvements: -- adapt to fixes and optimizations around skipped upserts in ash core +- [upserts] adapt to fixes and optimizations around skipped upserts in ash core ## [v2.4.6](https://github.com/ash-project/ash_postgres/compare/v2.4.5...v2.4.6) (2024-10-07) ### Improvements: -- with `--yes` assume oldest version +- [`mix ash_postgres.install`] with `--yes` assume oldest version ## [v2.4.5](https://github.com/ash-project/ash_postgres/compare/v2.4.4...v2.4.5) (2024-10-06) ### Bug Fixes: -- ensure upsert fields are uniq +- [upserts] ensure upsert fields are uniq ### Improvements: -- detect 1 arg repo use in installer +- [`mix ash_postgres.install`] detect 1 arg repo use in installer -- support to_ecto(%Ecto.Changeset{}) and from_ecto(%Ecto.Changeset{}) (#395) +- [`AshPostgres.Repo`] support to_ecto(%Ecto.Changeset{}) and from_ecto(%Ecto.Changeset{}) (#395) ## [v2.4.4](https://github.com/ash-project/ash_postgres/compare/v2.4.3...v2.4.4) (2024-09-29) ### Bug Fixes: -- handle atomic array operations +- [atomic updates] handle atomic array operations ## [v2.4.3](https://github.com/ash-project/ash_postgres/compare/v2.4.2...v2.4.3) (2024-09-27) ### Bug Fixes: -- support pg <= 14 in resource generator, and update tests +- [`mix ash_postgres.gen.resources`] support pg <= 14 in resource generator, and update tests ## [v2.4.2](https://github.com/ash-project/ash_postgres/compare/v2.4.1...v2.4.2) (2024-09-24) ### Bug Fixes: -- typo of `biging` -> `bigint` +- [migration generator] typo of `biging` -> `bigint` -- altering attributes not properly generating foreign keys in some cases +- [migration generator] altering attributes not properly generating foreign keys in some cases -- installer: use correct module name in the `DataCase` moduledocs. (#393) +- [`mix ash_postres.install`] use correct module name in the `DataCase` moduledocs. (#393) -- trim input before passing to `String.to_integer/1`. (#389) +- [migration generator] trim input before passing to `String.to_integer/1`. (#389) ### Improvements: -- add `--repo` option to installer, and warn on clashing existing repo +- [`mix ash_postgres.install`] add `--repo` option to installer, and warn on clashing existing repo -- prompt for minimum pg version +- [`mix ash_postgres.install`] prompt for minimum pg version -- adjust mix task aliases to be used with `ash_postgres` +- [`mix ash_postgres.install`] adjust mix task aliases to be used with `ash_postgres` -- set a name for generated migrations +- [migration generator] set a name for generated migrations ## [v2.4.1](https://github.com/ash-project/ash_postgres/compare/v2.4.0...v2.4.1) (2024-09-16) ### Bug Fixes: -- ensure that returning is not an empty list +- [bulk updates] ensure that returning is never an empty list -- match on table schema as well as table name +- [`mix ash_postgres.gen.resources`] match on table schema as well as table name ## [v2.4.0](https://github.com/ash-project/ash_postgres/compare/v2.3.1...v2.4.0) (2024-09-13) ### Features: -- Implement Ltree Type (#385) +- [`AshPostgres.Ltree`] Implement Ltree Type (#385) ### Improvements: -- update ash to latest version - -- remove LEAKPROOF from function to prevent migration issues - -- support upcoming `action_select` options +- [migration generator] remove LEAKPROOF from function to prevent migration issues -- ensure `Repo` is started after telemetry in igniter installer +- [`Ash.Changeset`] support upcoming `action_select` options -- update to latest igniter functions +- [`mix ash.install`] ensure `Repo` is started after telemetry in igniter installer ## [v2.3.1](https://github.com/ash-project/ash_postgres/compare/v2.3.0...v2.3.1) (2024-09-05) diff --git a/mix.exs b/mix.exs index cdf67ee7..323f719c 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.11" + @version "2.4.12" def project do [ From 8e909b9648c9a79e7bd6f0c12f39e5cf71bbd7cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:48:42 -0400 Subject: [PATCH 0773/1215] chore(deps): bump ash_sql in the production-dependencies group (#415) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.37 to 0.2.38 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.37...v0.2.38) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 49f4391e..594832f6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.37", "30be35acd32cd5995d08a30cda4b0c7cc33f8c343911e6115131ec858b5fd01a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2.5", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d8281fea518299ee165ff2704de0c769d838937b48258d1a94a3fc8000d32490"}, - "ash_sql": {:hex, :ash_sql, "0.2.37", "ec29820a501a9e7af57bae182741d13d9a71742019d71a5bfeac819346de0186", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "abd87869b8ebbd999491b947bb2f22c2e01e67fd3aa9b0fec68ab4f624e1da24"}, + "ash_sql": {:hex, :ash_sql, "0.2.38", "c04ebad69a4f69e73824ba630688052cfa6805e73c529046c1b2a480db148ee2", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "12e73244822d56c68de71927eb4eeb1a39978b746d4109474aa5e5efdbaa9e32"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, @@ -39,7 +39,7 @@ "sourceror": {:hex, :sourceror, "1.7.0", "62c34f4e3a109d837edd652730219b6332745e0ec7db34d5d350a5cdf30b376a", [:mix], [], "hexpm", "3dd2b1bd780fd0df48089f48480a54fd065bf815b63ef8046219d7784e7435f3"}, "spark": {:hex, :spark, "2.2.35", "1c0bb30f340151eca24164885935de39e6ada4010555f444c813d0488990f8f3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f242d6385c287389034a0e146d8f025b5c9ab777f1ae5cf0fdfc9209db6ae748"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, - "splode": {:hex, :splode, "0.2.5", "3e6fab3efb7340c5a67f906a69f8048db900f39a429c9275d9edb0a993b8be4d", [:mix], [], "hexpm", "ce95e724a1f468b547e6825f825267c6a0af13dcad0bfc23b10e1c7982a92f09"}, + "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, From 67f65260d708f9bf93fffffa4d7c23085d1a3768 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 31 Oct 2024 21:55:05 -0400 Subject: [PATCH 0774/1215] chore: update ash & tests --- mix.lock | 2 +- test/filter_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 594832f6..73c0f0c5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.37", "30be35acd32cd5995d08a30cda4b0c7cc33f8c343911e6115131ec858b5fd01a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2.5", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d8281fea518299ee165ff2704de0c769d838937b48258d1a94a3fc8000d32490"}, + "ash": {:hex, :ash, "3.4.39", "2aa016fa121e98e1cd4237a76bb8698e1715d5973edc78bbbc5ba0ec918040d3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a3e3b0dcd716aba8adc073119086e105022137e1aad12e574ca51cb6ddb786c7"}, "ash_sql": {:hex, :ash_sql, "0.2.38", "c04ebad69a4f69e73824ba630688052cfa6805e73c529046c1b2a480db148ee2", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "12e73244822d56c68de71927eb4eeb1a39978b746d4109474aa5e5efdbaa9e32"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/filter_test.exs b/test/filter_test.exs index fb36933c..83bcbba0 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -49,7 +49,7 @@ defmodule AshPostgres.FilterTest do test "it raises if you try to use ci_string while ci_text is not installed" do Application.put_env(:ash_postgres, :no_extensions, ["citext"]) - assert_raise Ash.Error.Query.InvalidExpression, fn -> + assert_raise Ash.Error.Invalid, fn -> Post |> Ash.Query.filter(category == "blah") |> Ash.read!() From 99acc0f40ce503a7aadecda1ee454b9457396075 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 2 Nov 2024 19:58:07 -0400 Subject: [PATCH 0775/1215] chore: tests of various features --- config/config.exs | 2 ++ mix.lock | 4 +-- test/custom_expression_test.exs | 17 +++++++++++ test/load_test.exs | 38 ++++++++++++++++++++++++- test/support/resources/post.ex | 9 ++++++ test/support/resources/profile.ex | 24 ++++++++++++++++ test/support/trigram_word_similarity.ex | 14 +++++++++ 7 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 test/custom_expression_test.exs create mode 100644 test/support/trigram_word_similarity.ex diff --git a/config/config.exs b/config/config.exs index 6861abc5..2a17cfd8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -25,6 +25,8 @@ if Mix.env() == :test do config :ash_postgres, :ash_domains, [AshPostgres.Test.Domain] + config :ash, :custom_expressions, [AshPostgres.Expressions.TrigramWordSimilarity] + config :ash_postgres, AshPostgres.TestRepo, username: "postgres", database: "ash_postgres_test", diff --git a/mix.lock b/mix.lock index 73c0f0c5..b6cfe550 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, - "igniter": {:hex, :igniter, "0.3.76", "ff283416402f4d1ef3f79ab57d38aac08389b3768fc81da03795ce5347f1167f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4692f874f969dc4856167469a3415a5a57a362314f37e1cdb14431316a74e896"}, + "igniter": {:hex, :igniter, "0.4.1", "74a6a376340755120a4c48949014ac09e95d1ffd918e73aea25fe8a4e405243e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c25a41acb4bb719ef1f0f847e9b999c995e3edf85ec631a404d5e035fea9c335"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -36,7 +36,7 @@ "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.7.0", "62c34f4e3a109d837edd652730219b6332745e0ec7db34d5d350a5cdf30b376a", [:mix], [], "hexpm", "3dd2b1bd780fd0df48089f48480a54fd065bf815b63ef8046219d7784e7435f3"}, + "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, "spark": {:hex, :spark, "2.2.35", "1c0bb30f340151eca24164885935de39e6ada4010555f444c813d0488990f8f3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f242d6385c287389034a0e146d8f025b5c9ab777f1ae5cf0fdfc9209db6ae748"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, diff --git a/test/custom_expression_test.exs b/test/custom_expression_test.exs new file mode 100644 index 00000000..e88737ad --- /dev/null +++ b/test/custom_expression_test.exs @@ -0,0 +1,17 @@ +defmodule AshPostgres.Test.CustomExpressionTest do + use AshPostgres.RepoCase, async: false + + test "unique constraint errors are properly caught" do + Ash.create!(AshPostgres.Test.Profile, %{description: "foo"}) + + assert [_] = + AshPostgres.Test.Profile + |> Ash.Query.for_read(:by_indirectly_matching_description, %{term: "fop"}) + |> Ash.read!() + + assert [_] = + AshPostgres.Test.Profile + |> Ash.Query.for_read(:by_directly_matching_description, %{term: "fop"}) + |> Ash.read!() + end +end diff --git a/test/load_test.exs b/test/load_test.exs index dd9b0e9d..9261e084 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,16 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Post, Record, StatefulPostFollower, TempEntity, User} + + alias AshPostgres.Test.{ + Author, + Comment, + Post, + PostFollower, + Record, + StatefulPostFollower, + TempEntity, + User + } require Ash.Query @@ -130,6 +140,32 @@ defmodule AshPostgres.Test.LoadTest do assert length(post.active_followers) == 2 end + test "many_to_many loads work with filter on the join relationship via the parent" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() + + for i <- 1..2 do + user = + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + + PostFollower + |> Ash.Changeset.for_create(:create, %{order: i, post_id: post.id, follower_id: user.id}) + |> Ash.create!() + end + + [post] = + Post + |> Ash.Query.for_read(:read, %{}) + |> Ash.Query.load(:first_3_followers) + |> Ash.read!() + + assert length(post.first_3_followers) == 2 + end + test "many_to_many loads work when nested" do source_post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 7d2764cb..db11f897 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -524,6 +524,15 @@ defmodule AshPostgres.Test.Post do destination_attribute_on_join_resource: :follower_id ) + many_to_many(:first_3_followers, AshPostgres.Test.User, + public?: true, + through: AshPostgres.Test.PostFollower, + join_relationship: :first_three_followers_assoc, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :follower_id, + filter: expr(parent(first_three_followers_assoc.order) <= 3) + ) + many_to_many(:stateful_followers, AshPostgres.Test.User, public?: true, through: AshPostgres.Test.StatefulPostFollower, diff --git a/test/support/resources/profile.ex b/test/support/resources/profile.ex index 0455f9b9..862887c4 100644 --- a/test/support/resources/profile.ex +++ b/test/support/resources/profile.ex @@ -19,6 +19,30 @@ defmodule AshPostgres.Test.Profile do default_accept(:*) defaults([:create, :read, :update, :destroy]) + + read :by_indirectly_matching_description do + argument :term, :string do + allow_nil?(false) + end + + filter(expr(calc_word_similarity(term: ^arg(:term)) > 0.2)) + end + + read :by_directly_matching_description do + argument :term, :string do + allow_nil?(false) + end + + filter(expr(trigram_word_similarity(description, ^arg(:term)) > 0.2)) + end + end + + calculations do + calculate :calc_word_similarity, + :float, + expr(trigram_word_similarity(description, ^arg(:term))) do + argument(:term, :string, allow_nil?: false) + end end relationships do diff --git a/test/support/trigram_word_similarity.ex b/test/support/trigram_word_similarity.ex new file mode 100644 index 00000000..4b7d0bfb --- /dev/null +++ b/test/support/trigram_word_similarity.ex @@ -0,0 +1,14 @@ +defmodule AshPostgres.Expressions.TrigramWordSimilarity do + @moduledoc false + use Ash.CustomExpression, + name: :trigram_word_similarity, + arguments: [[:string, :string]], + # setting to true does not seem to change the behaviour observed in this ticket + predicate?: false + + def expression(data_layer, [left, right]) when data_layer in [AshPostgres.DataLayer] do + {:ok, expr(fragment("word_similarity(?, ?)", ^left, ^right))} + end + + def expression(_data_layer, _args), do: :unknown +end From faaa439162e362c56fa82ddbfc9c2f184f6b2dfa Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 3 Nov 2024 16:44:23 -0500 Subject: [PATCH 0776/1215] improvement: don't generate task aliases that run seeds in test --- lib/mix/tasks/ash_postgres.install.ex | 13 ++++++++----- mix.exs | 2 +- mix.lock | 7 ++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 51af5713..93d061b2 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -87,12 +87,15 @@ defmodule Mix.Tasks.AshPostgres.Install do defp run_seeds_on_setup(igniter) do if Igniter.exists?(igniter, "priv/repo/seeds.exs") do - Igniter.Project.TaskAliases.add_alias(igniter, "ash.setup", [ - "ash.setup", - "run priv/repo/seeds.exs" - ]) - else igniter + |> Igniter.Project.TaskAliases.add_alias("setup", "ash.setup", + if_exists: {:replace_or_append, "ecto.setup", "ash.setup"} + ) + |> Igniter.Project.TaskAliases.add_alias("setup", "run priv/repo/seeds.exs", + if_exists: :append + ) + else + Igniter.Project.TaskAliases.add_alias(igniter, "setup", "ash.setup") end end diff --git a/mix.exs b/mix.exs index 323f719c..5c37a0f7 100644 --- a/mix.exs +++ b/mix.exs @@ -166,7 +166,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.4 and >= 3.4.37")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.37")}, - {:igniter, "~> 0.3 and >= 0.3.42"}, + {:igniter, "~> 0.4 and >= 0.4.4"}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index b6cfe550..763e41ae 100644 --- a/mix.lock +++ b/mix.lock @@ -19,8 +19,8 @@ "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, - "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, - "igniter": {:hex, :igniter, "0.4.1", "74a6a376340755120a4c48949014ac09e95d1ffd918e73aea25fe8a4e405243e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c25a41acb4bb719ef1f0f847e9b999c995e3edf85ec631a404d5e035fea9c335"}, + "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, + "igniter": {:hex, :igniter, "0.4.4", "10cbaf00cecc46a0c6c656556e2c106177959a06a962f969e5ac7eecc989207d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9a6ad8188e802f80394602807a36348eaa1c9e2c474cabadf8f2c1046d56c267"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -33,7 +33,7 @@ "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, "postgrex": {:hex, :postgrex, "0.19.2", "34d6884a332c7bf1e367fc8b9a849d23b43f7da5c6e263def92784d03f9da468", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "618988886ab7ae8561ebed9a3c7469034bf6a88b8995785a3378746a4b9835ec"}, "reactor": {:hex, :reactor, "0.10.0", "1206113c21ba69b889e072b2c189c05a7aced523b9c3cb8dbe2dab7062cb699a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4003c33e4c8b10b38897badea395e404d74d59a31beb30469a220f2b1ffe6457"}, - "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, + "rewrite": {:hex, :rewrite, "1.0.1", "2a249d703e47c050ad251fa43a3d019d4c08159ead95ec30ef48357ba88af609", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "79869f0bdb22840cf233b99e0dc7b6682a35d7e4747bdf2e78d3bc156b2c7c14"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, @@ -43,6 +43,7 @@ "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, } From d8e88fa864ebad9850b5611f965fa78eaacd9e75 Mon Sep 17 00:00:00 2001 From: Adebisi Adeyeye <68188123+badeyeye1@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:14:21 +0100 Subject: [PATCH 0777/1215] docs: fix type in docs (#417) Co-authored-by: Adebisi Adeyeye --- documentation/topics/advanced/expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/topics/advanced/expressions.md b/documentation/topics/advanced/expressions.md index 3195c58c..953e402d 100644 --- a/documentation/topics/advanced/expressions.md +++ b/documentation/topics/advanced/expressions.md @@ -26,7 +26,7 @@ fragment("repeat('hello', 4)") fragment("points > (SELECT SUM(points) FROM games WHERE user_id = ? AND id != ?)", user_id, id) ``` -> ### a last resport {: .warning} +> ### a last resort {: .warning} > > Using entire queries as shown above is a last resort, but can sometimes be the best way to accomplish a given task. From 48b3f6c83202eb53ddc4acf0e84f4ab4edf8ab77 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 4 Nov 2024 10:29:52 -0500 Subject: [PATCH 0778/1215] improvement: add tests for calculations through from_many? relationships test: improve test QOL with ecto dev logger --- config/config.exs | 3 ++ mix.exs | 3 +- mix.lock | 1 + test/complex_calculations_test.exs | 23 +++++++++++++++ .../resources/channel_member.ex | 8 +++++ test/test_helper.exs | 29 +++++++++++++++++++ 6 files changed, 66 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 2a17cfd8..62650332 100644 --- a/config/config.exs +++ b/config/config.exs @@ -18,6 +18,9 @@ if Mix.env() == :dev do end if Mix.env() == :test do + config :ash_postgres, AshPostgres.TestRepo, log: false + config :ash_postgres, AshPostgres.TestNoSandboxRepo, log: false + config :ash, :validate_domain_resource_inclusion?, false config :ash, :validate_domain_config_inclusion?, false diff --git a/mix.exs b/mix.exs index 5c37a0f7..4ed1dc33 100644 --- a/mix.exs +++ b/mix.exs @@ -165,7 +165,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.37")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.37")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.39")}, {:igniter, "~> 0.4 and >= 0.4.4"}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, @@ -174,6 +174,7 @@ defmodule AshPostgres.MixProject do {:inflex, "~> 2.1"}, {:owl, "~> 0.11"}, # dev/test dependencies + {:ecto_dev_logger, "~> 0.14", only: :test}, {:eflame, "~> 1.0", only: [:dev, :test]}, {:simple_sat, "~> 0.1", only: [:dev, :test]}, {:benchee, "~> 1.1", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 763e41ae..fc77391b 100644 --- a/mix.lock +++ b/mix.lock @@ -10,6 +10,7 @@ "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.0", "048e20ea5e3b9c744efda9ed838b8f1e4c180020163afeec102b95143dfdce0a", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5.1", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "44691ba46409be073448764e3557694e5a022dcaa94b11a3d0b73b9066e1b224"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index dfa5e13d..383a0ac9 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -226,6 +226,29 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do assert channel.dm_name == user_2.name end + test "calculations through `from_many?` relationships properly join" do + dm_channel = + AshPostgres.Test.ComplexCalculations.DMChannel + |> Ash.Changeset.new() + |> Ash.create!() + + user_1 = + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "User 1"}) + |> Ash.create!() + + AshPostgres.Test.ComplexCalculations.ChannelMember + |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_1.id}) + |> Ash.create!() + + assert channel_member = + AshPostgres.Test.ComplexCalculations.ChannelMember + |> Ash.Query.load(:first_member_recently_created) + |> Ash.read_one!() + + assert channel_member.first_member_recently_created + end + test "calculations with parent filters can be filtered on themselves" do AshPostgres.Test.ComplexCalculations.DMChannel |> Ash.Changeset.new() diff --git a/test/support/complex_calculations/resources/channel_member.ex b/test/support/complex_calculations/resources/channel_member.ex index 69e631e4..c9d99997 100644 --- a/test/support/complex_calculations/resources/channel_member.ex +++ b/test/support/complex_calculations/resources/channel_member.ex @@ -18,6 +18,14 @@ defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do update_timestamp(:updated_at, public?: true) end + calculations do + calculate( + :first_member_recently_created, + :boolean, + expr(channel.first_member.created_at > ago(1, :day)) + ) + end + postgres do table "complex_calculations_certifications_channel_members" repo(AshPostgres.TestRepo) diff --git a/test/test_helper.exs b/test/test_helper.exs index 5b02474b..531e4ba8 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -22,3 +22,32 @@ ExUnit.configure(stacktrace_depth: 100, exclude: exclude_tags) AshPostgres.TestRepo.start_link() AshPostgres.TestNoSandboxRepo.start_link() + +format_sql_query = + try do + case System.shell("which pg_format") do + {_, 0} -> + fn query -> + try do + case System.shell("echo $SQL_QUERY | pg_format -", + env: [{"SQL_QUERY", query}], + stderr_to_stdout: true + ) do + {formatted_query, 0} -> String.trim_trailing(formatted_query) + _ -> query + end + rescue + _ -> query + end + end + + _ -> + & &1 + end + rescue + _ -> + & &1 + end + +Ecto.DevLogger.install(AshPostgres.TestRepo, before_inline_callback: format_sql_query) +Ecto.DevLogger.install(AshPostgres.TestNoSandboxRepo, before_inline_callback: format_sql_query) From 02718dc07e8c5b1d3598a54ed87e556169c5ba75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 02:54:26 -0500 Subject: [PATCH 0779/1215] chore(deps-dev): bump the dev-dependencies group across 1 directory with 3 updates (#428) --- mix.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mix.lock b/mix.lock index fc77391b..76ce8b9c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,16 +1,16 @@ %{ - "ash": {:hex, :ash, "3.4.39", "2aa016fa121e98e1cd4237a76bb8698e1715d5973edc78bbbc5ba0ec918040d3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.61 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a3e3b0dcd716aba8adc073119086e105022137e1aad12e574ca51cb6ddb786c7"}, - "ash_sql": {:hex, :ash_sql, "0.2.38", "c04ebad69a4f69e73824ba630688052cfa6805e73c529046c1b2a480db148ee2", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "12e73244822d56c68de71927eb4eeb1a39978b746d4109474aa5e5efdbaa9e32"}, + "ash": {:hex, :ash, "3.4.43", "5ffd63e49b71ceaf7ed15efd20cd60ec28e9c55e28fca32b24dcc690644f2656", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b551208878c8e91f4ac3d3b6aee037f071a90a9c1151360f2a658e3b0aadf17"}, + "ash_sql": {:hex, :ash_sql, "0.2.39", "75411bea310ce514c16acd0855bdb9a754dba838fa566e1f053591fbd3da0dfb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "012904de597b9132b7dfdaf073ed8b0897f78ff9263f4036478cece4ac1fa97f"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, + "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, - "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, + "decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, - "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.0", "048e20ea5e3b9c744efda9ed838b8f1e4c180020163afeec102b95143dfdce0a", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5.1", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "44691ba46409be073448764e3557694e5a022dcaa94b11a3d0b73b9066e1b224"}, + "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.4.4", "10cbaf00cecc46a0c6c656556e2c106177959a06a962f969e5ac7eecc989207d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9a6ad8188e802f80394602807a36348eaa1c9e2c474cabadf8f2c1046d56c267"}, + "igniter": {:hex, :igniter, "0.4.7", "a91ab006e400f82ae93c1ada7d112cc7e7c1684455428c0de98d6b2e66025b43", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "89bc7a093d60c5c70e46014ffd83d993593c1151c8301e3a16f2846695ff22ec"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -32,9 +32,9 @@ "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, - "postgrex": {:hex, :postgrex, "0.19.2", "34d6884a332c7bf1e367fc8b9a849d23b43f7da5c6e263def92784d03f9da468", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "618988886ab7ae8561ebed9a3c7469034bf6a88b8995785a3378746a4b9835ec"}, - "reactor": {:hex, :reactor, "0.10.0", "1206113c21ba69b889e072b2c189c05a7aced523b9c3cb8dbe2dab7062cb699a", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4003c33e4c8b10b38897badea395e404d74d59a31beb30469a220f2b1ffe6457"}, - "rewrite": {:hex, :rewrite, "1.0.1", "2a249d703e47c050ad251fa43a3d019d4c08159ead95ec30ef48357ba88af609", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "79869f0bdb22840cf233b99e0dc7b6682a35d7e4747bdf2e78d3bc156b2c7c14"}, + "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, + "reactor": {:hex, :reactor, "0.10.1", "7aad41c6f88c5214c5f878c597bc64d0f9983af3003bf2a6dbece031630a71b7", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b1aca34c0eafaefa98163778fc8252273638e36d0a70165b752910015161b6a8"}, + "rewrite": {:hex, :rewrite, "1.1.1", "0e6674eb5f8cb11aabe5ad6207151b4156bf173aa9b43133a68f8cc882364570", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "fcd688b3ca543c3a1f1f4615ccc054ec37cfcde91133a27a683ec09b35ae1496"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, From 05d417c27fcd8c87802fc055bbd7abe69df9da87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 02:54:41 -0500 Subject: [PATCH 0780/1215] chore(deps): bump the production-dependencies group across 1 directory with 4 updates (#429) From 0bfc8b87ec49f17d0af12ebfdeee9b0e63854982 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 12:38:56 -0500 Subject: [PATCH 0781/1215] improvement: honor `:priv` in migration generator, and make it explicitly configurable closes #426 --- lib/migration_generator/migration_generator.ex | 11 +++++++++-- lib/repo.ex | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 779538e7..112fb321 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -159,8 +159,15 @@ defmodule AshPostgres.MigrationGenerator do # Copied from ecto's mix task, thanks Ecto ❤️ config = repo.config() - app = Keyword.fetch!(config, :otp_app) - Path.join([Mix.Project.deps_paths()[app] || File.cwd!(), "priv", "resource_snapshots"]) + if snapshot_path = opts[:snapshots_path] do + snapshot_path + else + priv = + config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + + app = Keyword.fetch!(config, :otp_app) + Application.app_dir(app, Path.join(priv, "resource_snapshots")) + end end defp create_extension_migrations(repos, opts) do diff --git a/lib/repo.ex b/lib/repo.ex index 5cc66e8d..6f77f300 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -39,6 +39,15 @@ defmodule AshPostgres.Repo do ```elixir %{type: :create, %{resource: YourApp.YourResource, action: :action}} ``` + + ## Additional Repo Configuration + + Because an `AshPostgres.Repo` is also an `Ecto.Repo`, it has all of the same callbacks. + + In the `c:Ecto.Repo.config/0` callback, you can configure the following additional items: + + - `:tenant_migrations_path` - The path where your tenant migrations are stored (only relevant for a multitenant implementation) + - `:snapshots_path` - The path where the resource snapshots for the migration generator are stored. """ @doc "Use this to inform the data layer about what extensions are installed" From 9eef1f48d0a927140ab07d935664a824dfd6f412 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 12:42:14 -0500 Subject: [PATCH 0782/1215] chore: fix typo --- lib/migration_generator/migration_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 112fb321..7a7a0005 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -159,7 +159,7 @@ defmodule AshPostgres.MigrationGenerator do # Copied from ecto's mix task, thanks Ecto ❤️ config = repo.config() - if snapshot_path = opts[:snapshots_path] do + if snapshot_path = config[:snapshots_path] do snapshot_path else priv = From 17abe3800fcebe51812a1fcf3587a53b42a4fd33 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 12:51:23 -0500 Subject: [PATCH 0783/1215] improvement: honor repo configuration in migration generator --- .../migration_generator.ex | 23 ++++++++++--------- .../tasks/ash_postgres.generate_migrations.ex | 6 ++--- lib/repo.ex | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7a7a0005..7ce9edab 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -865,27 +865,28 @@ defmodule AshPostgres.MigrationGenerator do end defp migration_path(opts, repo, tenant? \\ false) do - repo_name = repo_name(repo) # Copied from ecto's mix task, thanks Ecto ❤️ config = repo.config() app = Keyword.fetch!(config, :otp_app) if tenant? do - if opts.tenant_migration_path do - opts.tenant_migration_path + if path = opts.tenant_migration_path || config[:tenant_migrations_path] do + path else - Path.join([Mix.Project.deps_paths()[app] || File.cwd!(), "priv"]) + priv = + config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + + Application.app_dir(app, Path.join(priv, "tenant_migrations")) end - |> Path.join(repo_name) - |> Path.join("tenant_migrations") else - if opts.migration_path do - opts.migration_path + if path = opts.migration_path || config[:tenant_migrations_path] do + path else - Path.join([Mix.Project.deps_paths()[app] || File.cwd!(), "priv"]) + priv = + config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + + Application.app_dir(app, Path.join(priv, "migrations")) end - |> Path.join(repo_name) - |> Path.join("migrations") end end diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 08f46528..b71568d4 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -5,10 +5,10 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do Options: * `domains` - a comma separated list of Domain modules, for which migrations will be generated - * `snapshot-path` - a custom path to store the snapshots, defaults to "priv/resource_snapshots" - * `migration-path` - a custom path to store the migrations, defaults to "priv". + * `snapshot-path` - a custom path to store the snapshots, defaults to "priv/repo_name/resource_snapshots" + * `migration-path` - a custom path to store the migrations, defaults to "priv/repo_name/migrations". Migrations are stored in a folder for each repo, so `priv/repo_name/migrations` - * `tenant-migration-path` - Same as `migration_path`, except for any tenant specific migrations + * `tenant-migration-path` - Same as `migration_path`, except for tenant-specific migrations * `drop-columns` - whether or not to drop columns as attributes are removed. See below for more * `name` - names the generated migrations, prepending with the timestamp. The default is `migrate_resources_`, diff --git a/lib/repo.ex b/lib/repo.ex index 6f77f300..5429fdf6 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -44,7 +44,7 @@ defmodule AshPostgres.Repo do Because an `AshPostgres.Repo` is also an `Ecto.Repo`, it has all of the same callbacks. - In the `c:Ecto.Repo.config/0` callback, you can configure the following additional items: + In the `c:Ecto.Repo.init/2` callback, you can configure the following additional items: - `:tenant_migrations_path` - The path where your tenant migrations are stored (only relevant for a multitenant implementation) - `:snapshots_path` - The path where the resource snapshots for the migration generator are stored. From bd65cb6011257254d9717bcb41250f0efee366b7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 14:13:53 -0500 Subject: [PATCH 0784/1215] chore: small fixes from previous PRs --- .../migration_generator.ex | 43 ++++++++++++++----- lib/resource_generator/spec.ex | 4 +- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7ce9edab..55b82f61 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -143,13 +143,7 @@ defmodule AshPostgres.MigrationGenerator do end defp opts(opts) do - case struct(__MODULE__, opts) do - %{check: true} = opts -> - %{opts | dry_run: true} - - opts -> - opts - end + struct(__MODULE__, opts) end defp snapshot_path(%{snapshot_path: snapshot_path}, _) when not is_nil(snapshot_path), @@ -163,10 +157,17 @@ defmodule AshPostgres.MigrationGenerator do snapshot_path else priv = - config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + config[:priv] || "priv/" app = Keyword.fetch!(config, :otp_app) - Application.app_dir(app, Path.join(priv, "resource_snapshots")) + + Application.app_dir( + app, + Path.join([ + priv, + "resource_snapshots" + ]) + ) end end @@ -182,7 +183,7 @@ defmodule AshPostgres.MigrationGenerator do |> Path.join(repo_name) |> Path.join("extensions.json") - unless opts.dry_run do + unless opts.dry_run || opts.check do File.rename(legacy_snapshot_file, snapshot_file) end @@ -383,7 +384,29 @@ defmodule AshPostgres.MigrationGenerator do if opts.dry_run do Mix.shell().info(snapshot_contents) Mix.shell().info(contents) + + if opts.check do + Mix.shell().error(""" + Migrations would have been generated, but the --check flag was provided. + + To see what migration would have been generated, run with the `--dry-run` + option instead. To generate those migrations, run without either flag. + """) + + exit({:shutdown, 1}) + end else + if opts.check do + Mix.shell().error(""" + Migrations would have been generated, but the --check flag was provided. + + To see what migration would have been generated, run with the `--dry-run` + option instead. To generate those migrations, run without either flag. + """) + + exit({:shutdown, 1}) + end + create_file(snapshot_file, snapshot_contents, force: true) create_file(migration_file, contents) end diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index a2eaa83a..d3a256d1 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -897,9 +897,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do Process.put({:type_cache, attribute.type}, new_type) %{attribute | attr_type: new_type} rescue - e -> - IO.puts(Exception.format(:error, e, __STACKTRACE__)) - + _e -> get_type(attribute, opts) end end From 1cfb44614cefc26a05820eca1dd0f234ebb64c34 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 14:57:55 -0500 Subject: [PATCH 0785/1215] fix: honor the `snapshots_only` option fixes #427 --- .../migration_generator.ex | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 55b82f61..37d657ad 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -408,7 +408,10 @@ defmodule AshPostgres.MigrationGenerator do end create_file(snapshot_file, snapshot_contents, force: true) - create_file(migration_file, contents) + + if !opts.snapshots_only do + create_file(migration_file, contents) + end end end end @@ -469,23 +472,25 @@ defmodule AshPostgres.MigrationGenerator do exit({:shutdown, 1}) end - operations - |> split_into_migrations() - |> Enum.each(fn operations -> - run_without_transaction? = - Enum.any?(operations, fn - %Operation.AddCustomIndex{index: %{concurrently: true}} -> - true - - _ -> - false - end) - + if !opts.snapshots_only do operations - |> organize_operations - |> build_up_and_down() - |> write_migration!(repo, opts, tenant?, run_without_transaction?) - end) + |> split_into_migrations() + |> Enum.each(fn operations -> + run_without_transaction? = + Enum.any?(operations, fn + %Operation.AddCustomIndex{index: %{concurrently: true}} -> + true + + _ -> + false + end) + + operations + |> organize_operations + |> build_up_and_down() + |> write_migration!(repo, opts, tenant?, run_without_transaction?) + end) + end create_new_snapshot(snapshots, repo_name(repo), opts, tenant?) end @@ -1052,7 +1057,7 @@ defmodule AshPostgres.MigrationGenerator do end File.mkdir_p(Path.dirname(snapshot_file)) - File.write!(snapshot_file, snapshot_binary, []) + create_file(snapshot_file, snapshot_binary, force: true) old_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}.json") From 7bdb77d7eeca047b856a13655b9577dea6ef21e7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 19:58:02 -0500 Subject: [PATCH 0786/1215] chore: release version v2.4.13 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68267468..52d00b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.13](https://github.com/ash-project/ash_postgres/compare/v2.4.12...v2.4.13) (2024-11-26) + + + + +### Bug Fixes: + +* honor the `snapshots_only` option + +### Improvements: + +* honor repo configuration in migration generator + +* honor `:priv` in migration generator, and make it explicitly configurable + +* add tests for calculations through from_many? relationships + +* don't generate task aliases that run seeds in test + ## [v2.4.12](https://github.com/ash-project/ash_postgres/compare/v2.4.11...v2.4.12) (2024-10-30) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 4ed1dc33..2f17c8fb 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.12" + @version "2.4.13" def project do [ From dfb4efb8b4168c2d5d9dab3840582cf55bcd2bab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 25 Nov 2024 20:01:23 -0500 Subject: [PATCH 0787/1215] chore: update changelog --- CHANGELOG.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52d00b44..f85e62ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,36 +7,31 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide ## [v2.4.13](https://github.com/ash-project/ash_postgres/compare/v2.4.12...v2.4.13) (2024-11-26) - - - ### Bug Fixes: -* honor the `snapshots_only` option +- [`mix ash.migrate`] honor the `snapshots_only` option ### Improvements: -* honor repo configuration in migration generator - -* honor `:priv` in migration generator, and make it explicitly configurable +- [`mix ash.migrate`] honor repo configuration in migration generator -* add tests for calculations through from_many? relationships +- [`mix ash.codegen`] honor `:priv` in migration generator, and make it explicitly configurable -* don't generate task aliases that run seeds in test +- [`mix ash_postgres.install`] don't generate task aliases that run seeds in test ## [v2.4.12](https://github.com/ash-project/ash_postgres/compare/v2.4.11...v2.4.12) (2024-10-30) ### Bug Fixes: -* [query builder] don't double add distinct clauses +- [query builder] don't double add distinct clauses -* [`AshPostgres.DataLayer`] don't use `cast` for changes +- [`AshPostgres.DataLayer`] don't use `cast` for changes ### Improvements: -* [`AshPostgres.Repo`] set `prefer_transaction?` to false in generated repos +- [`AshPostgres.Repo`] set `prefer_transaction?` to false in generated repos -* [`AshPostgres.DataLayer`] support prefer_transaction? +- [`AshPostgres.DataLayer`] support prefer_transaction? ## [v2.4.11](https://github.com/ash-project/ash_postgres/compare/v2.4.10...v2.4.11) (2024-10-23) From 83e251d089cdf68b21938a0c79ff27fe9df85cf9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 26 Nov 2024 22:36:23 -0500 Subject: [PATCH 0788/1215] fix: pass AST to deal with stupid igniter behavior --- lib/mix/tasks/ash_postgres.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 93d061b2..341aa1a0 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -76,7 +76,7 @@ defmodule Mix.Tasks.AshPostgres.Install do &Igniter.Code.List.replace_in_list( &1, is_ecto_setup, - "ash.setup" + Sourceror.parse_string!("\"ash.setup\"") ) ) |> Igniter.Project.TaskAliases.add_alias("test", ["ash.setup --quiet", "test"], From 97568de74a2e07870e082d6f58bf12a8608e987a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 26 Nov 2024 22:37:28 -0500 Subject: [PATCH 0789/1215] chore: release version v2.4.14 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f85e62ff..62d7e4b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.14](https://github.com/ash-project/ash_postgres/compare/v2.4.13...v2.4.14) (2024-11-27) + + + + +### Bug Fixes: + +* pass AST to deal with stupid igniter behavior + ## [v2.4.13](https://github.com/ash-project/ash_postgres/compare/v2.4.12...v2.4.13) (2024-11-26) ### Bug Fixes: diff --git a/mix.exs b/mix.exs index 2f17c8fb..69599c3e 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.13" + @version "2.4.14" def project do [ From d1a434b930c22c240ec99d51fa1edb97e4da12d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 07:47:52 -0500 Subject: [PATCH 0790/1215] chore(deps): bump igniter in the production-dependencies group (#431) Bumps the production-dependencies group with 1 update: [igniter](https://github.com/ash-project/igniter). Updates `igniter` from 0.4.7 to 0.4.8 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.4.7...v0.4.8) --- updated-dependencies: - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 76ce8b9c..c1d1d1e1 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.4.7", "a91ab006e400f82ae93c1ada7d112cc7e7c1684455428c0de98d6b2e66025b43", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "89bc7a093d60c5c70e46014ffd83d993593c1151c8301e3a16f2846695ff22ec"}, + "igniter": {:hex, :igniter, "0.4.8", "6d1bf4934952ac3eb20f6cbac0d5cd6d8012e42e3de20ad794703556c14cfa08", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f9dd06f971fa053c6b0d9f8263b625f619a0fd3645d6a8cd6170935055a8f0df"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From d6bb54a961e0c6b6c2c1b3fe77aee699d02a2bec Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Dec 2024 13:11:27 -0500 Subject: [PATCH 0791/1215] chore: remove raised error --- lib/sql_implementation.ex | 4 ---- lib/types/ltree.ex | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index c5a13c0d..ae4f863d 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -239,10 +239,6 @@ defmodule AshPostgres.SqlImplementation do do: nil def parameterized_type(type, constraints, no_maps?) do - if type == :array do - raise "WHAT" - end - if Ash.Type.ash_type?(type) do cast_in_query? = if function_exported?(Ash.Type, :cast_in_query?, 2) do diff --git a/lib/types/ltree.ex b/lib/types/ltree.ex index 0d6ad79e..512758e2 100644 --- a/lib/types/ltree.ex +++ b/lib/types/ltree.ex @@ -5,15 +5,15 @@ defmodule AshPostgres.Ltree do doc: """ Escape the ltree segments to make it possible to include characters that are either `.` (the separation character) or any other unsupported - character like `-` (Postgres <= 15). - + character like `-` (Postgres <= 15). + If the option is enabled, any characters besides `[0-9a-zA-Z]` will be - replaced with `_[HEX Ascii Code]`. - + replaced with `_[HEX Ascii Code]`. + Additionally the type will no longer take strings as user input since it's impossible to decide between `.` being a separator or part of a - segment. - + segment. + If the option is disabled, any string will be relayed directly to postgres. If the segments are provided as a list, they can't contain `.` since postgres would split the segment. From 5dbeb57991951554a3106a72af3700048c21c965 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Dec 2024 19:54:31 -0500 Subject: [PATCH 0792/1215] improvement: update sql implementation for type determination --- lib/sql_implementation.ex | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index ae4f863d..dc65ec91 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -218,6 +218,10 @@ defmodule AshPostgres.SqlImplementation do end end + def parameterized_type({type, constraints}, [], no_maps?) do + parameterized_type(type, constraints, no_maps?) + end + def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) end @@ -279,28 +283,23 @@ defmodule AshPostgres.SqlImplementation do @impl true def determine_types(mod, args, returns \\ nil) do - {types, new_returns} = Ash.Expr.determine_types(mod, args, returns) + case returns do + {:parameterized, _} -> raise "what" + _ -> :ok + end - new_returns = - case new_returns do - {:parameterized, _} = parameterized -> parameterized - {:array, _} = type -> parameterized_type(type, []) - {type, constraints} -> parameterized_type(type, constraints) + returns = + case returns do + {:parameterized, _} -> nil + {:array, {:parameterized, _}} -> nil + {:array, {type, constraints}} when type != :array -> {type, [items: constraints]} + {:array, _} -> nil + {type, constraints} -> {type, constraints} other -> other end - {Enum.map(types, fn - {:parameterized, _} = parameterized -> - parameterized - - {:array, _} = type -> - parameterized_type(type, []) - - {type, constraints} -> - parameterized_type(type, constraints) + {types, new_returns} = Ash.Expr.determine_types(mod, args, returns) - other -> - other - end), new_returns || returns} + {types, new_returns || returns} end end From 26291ddd79d3ca5b24367da5ebe5dc0dd06785fd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Dec 2024 20:06:59 -0500 Subject: [PATCH 0793/1215] chore: don't raise debugging error --- lib/sql_implementation.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index dc65ec91..b9a74f93 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -283,11 +283,6 @@ defmodule AshPostgres.SqlImplementation do @impl true def determine_types(mod, args, returns \\ nil) do - case returns do - {:parameterized, _} -> raise "what" - _ -> :ok - end - returns = case returns do {:parameterized, _} -> nil From 28f863cfef18d830cc6c1f07c29c3f32fce2db25 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Dec 2024 20:07:52 -0500 Subject: [PATCH 0794/1215] fix: don't use `priv` configuration for snapshot_path fixes #432 --- lib/migration_generator/migration_generator.ex | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 37d657ad..df32e52f 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -156,17 +156,13 @@ defmodule AshPostgres.MigrationGenerator do if snapshot_path = config[:snapshots_path] do snapshot_path else - priv = - config[:priv] || "priv/" + priv = "priv/" app = Keyword.fetch!(config, :otp_app) Application.app_dir( app, - Path.join([ - priv, - "resource_snapshots" - ]) + ["priv", "resource_snapshots"] ) end end From 8ff7534e56193c2a30ac7be8b6228382b80904cf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Dec 2024 16:13:24 -0500 Subject: [PATCH 0795/1215] chore: remove unused variable --- lib/migration_generator/migration_generator.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index df32e52f..110725fc 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -156,8 +156,6 @@ defmodule AshPostgres.MigrationGenerator do if snapshot_path = config[:snapshots_path] do snapshot_path else - priv = "priv/" - app = Keyword.fetch!(config, :otp_app) Application.app_dir( From 813944bc4558bf437f4ab30c70ffbad1d15ca563 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Dec 2024 16:35:23 -0500 Subject: [PATCH 0796/1215] fix: handle manual/no_attributes? relationships in lateral join logic --- lib/data_layer.ex | 81 +++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1efc3caa..a0be3ab6 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -983,6 +983,13 @@ defmodule AshPostgres.DataLayer do repo = AshSql.dynamic_repo(source_resource, AshPostgres.SqlImplementation, lateral_join_query) + # patching strange behavior that sets `take` to this empty list even though I'm not telling it to + lateral_join_query = + case lateral_join_query do + %{select: %{take: %{0 => {:map, []}}}} -> put_in(lateral_join_query.select.take, %{}) + _ -> lateral_join_query + end + results = repo.all( lateral_join_query, @@ -1058,46 +1065,52 @@ defmodule AshPostgres.DataLayer do {:ok, data_layer_query} -> source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) - source_filter = - case source_pkey do - [] -> - Ecto.Query.dynamic([source], field(source, ^source_attribute) in ^source_values) - - [field] -> - values = Enum.map(root_data, &Map.get(&1, field)) - Ecto.Query.dynamic([source], field(source, ^field) in ^values) + data_layer_query = + if Map.get(relationship, :manual) || Map.get(relationship, :no_attributes?) do + data_layer_query + else + source_filter = + case source_pkey do + [] -> + Ecto.Query.dynamic([source], field(source, ^source_attribute) in ^source_values) + + [field] -> + values = Enum.map(root_data, &Map.get(&1, field)) + Ecto.Query.dynamic([source], field(source, ^field) in ^values) + + fields -> + Enum.reduce(root_data, nil, fn record, acc -> + row_match = + Enum.reduce(fields, nil, fn field, acc -> + if is_nil(acc) do + Ecto.Query.dynamic( + [source], + field(source, ^field) == ^Map.get(record, field) + ) + else + Ecto.Query.dynamic( + [source], + field(source, ^field) == ^Map.get(record, field) and ^acc + ) + end + end) - fields -> - Enum.reduce(root_data, nil, fn record, acc -> - row_match = - Enum.reduce(fields, nil, fn field, acc -> if is_nil(acc) do - Ecto.Query.dynamic( - [source], - field(source, ^field) == ^Map.get(record, field) - ) + row_match else - Ecto.Query.dynamic( - [source], - field(source, ^field) == ^Map.get(record, field) and ^acc - ) + Ecto.Query.dynamic(^row_match or ^acc) end end) + end - if is_nil(acc) do - row_match - else - Ecto.Query.dynamic(^row_match or ^acc) - end - end) + from(source in data_layer_query, + where: ^source_filter + ) end data_layer_query = - from(source in data_layer_query, - where: ^source_filter - ) - - data_layer_query = Ecto.Query.exclude(data_layer_query, :distinct) + data_layer_query + |> Ecto.Query.exclude(:distinct) if query.__ash_bindings__[:__order__?] do {:ok, @@ -1105,8 +1118,7 @@ defmodule AshPostgres.DataLayer do inner_lateral_join: destination in ^subquery, on: true, order_by: destination.__order__, - select: destination, - select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, + select: merge(destination, %{__lateral_join_source__: map(source, ^source_pkey)}), distinct: true )} else @@ -1114,8 +1126,7 @@ defmodule AshPostgres.DataLayer do from(source in data_layer_query, inner_lateral_join: destination in ^subquery, on: true, - select: destination, - select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, + select: merge(destination, %{__lateral_join_source__: map(source, ^source_pkey)}), distinct: true )} end From 715575467a1ba09328c45f4a06cec2b94ad7e54c Mon Sep 17 00:00:00 2001 From: Joshua Hansen Date: Wed, 4 Dec 2024 16:56:50 -0700 Subject: [PATCH 0797/1215] docs: add configuration example and reference in exceptions (#435) * docs: add configuration example and reference in exceptions * doc: replace module references with hex URLs This makes for better developer experience with terminals smart enough to use hyperlinks. --- .../dsls/DSL-AshPostgres.DataLayer.md | 2 +- lib/data_layer.ex | 10 ++++--- lib/data_layer/info.ex | 30 ++++++++++++++++++- .../migration_generator.ex | 8 +++-- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/documentation/dsls/DSL-AshPostgres.DataLayer.md b/documentation/dsls/DSL-AshPostgres.DataLayer.md index 305a925f..4ad2f0b3 100644 --- a/documentation/dsls/DSL-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL-AshPostgres.DataLayer.md @@ -44,7 +44,7 @@ end | [`migration_types`](#postgres-migration_types){: #postgres-migration_types } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration type that should be used for that attribute. Only necessary if you need to override the defaults. | | [`migration_defaults`](#postgres-migration_defaults){: #postgres-migration_defaults } | `keyword` | `[]` | A keyword list of attribute names to the ecto migration default that should be used for that attribute. The string you use will be placed verbatim in the migration. Use fragments like `fragment(\\"now()\\")`, or for `nil`, use `\\"nil\\"`. | | [`calculations_to_sql`](#postgres-calculations_to_sql){: #postgres-calculations_to_sql } | `keyword` | | A keyword list of calculations and their SQL representation. Used when creating unique indexes for identities over calculations | -| [`identity_wheres_to_sql`](#postgres-identity_wheres_to_sql){: #postgres-identity_wheres_to_sql } | `keyword` | | A keyword list of identity names and the SQL representation of their `where` clause. Used when creating unique indexes for identities over calculations | +| [`identity_wheres_to_sql`](#postgres-identity_wheres_to_sql){: #postgres-identity_wheres_to_sql } | `keyword` | | A keyword list of identity names and the SQL representation of their `where` clause. See `AshPostgres.DataLayer.Info.identity_wheres_to_sql/1` for more details. | | [`base_filter_sql`](#postgres-base_filter_sql){: #postgres-base_filter_sql } | `String.t` | | A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter | | [`simple_join_first_aggregates`](#postgres-simple_join_first_aggregates){: #postgres-simple_join_first_aggregates } | `list(atom)` | `[]` | A list of `:first` type aggregate names that can be joined to using a simple join. Use when you have a `:first` aggregate that uses a to-many relationship , but your `filter` statement ensures that there is only one result. Optimizes the generated query. | | [`skip_unique_indexes`](#postgres-skip_unique_indexes){: #postgres-skip_unique_indexes } | `atom \| list(atom)` | `[]` | Skip generating unique indexes when generating migrations | diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a0be3ab6..670567ba 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -315,7 +315,7 @@ defmodule AshPostgres.DataLayer do identity_wheres_to_sql: [ type: :keyword_list, doc: - "A keyword list of identity names and the SQL representation of their `where` clause. Used when creating unique indexes for identities over calculations" + "A keyword list of identity names and the SQL representation of their `where` clause. See `AshPostgres.DataLayer.Info.identity_wheres_to_sql/1` for more details." ], base_filter_sql: [ type: :string, @@ -2668,9 +2668,11 @@ defmodule AshPostgres.DataLayer do case identity do %{name: name, where: where} when not is_nil(where) -> AshPostgres.DataLayer.Info.identity_where_to_sql(resource, name) || - raise( - "Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql` to use it as an upsert_identity" - ) + raise(""" + Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql` to use it as an upsert_identity. + + See https://hexdocs.pm/ash_postgres/AshPostgres.DataLayer.Info.html#identity_wheres_to_sql/1 for an example. + """) _ -> nil diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 7f66ff2c..696439ae 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -23,11 +23,39 @@ defmodule AshPostgres.DataLayer.Info do calculations_to_sql(resource)[calc] end - @doc "A keyword list of identity names to the sql representation of their where clauses" + @doc """ + A keyword list of identity names to the literal SQL string representation of + the `where` clause portion of identity's partial unique index. + + For example, given the following identity for a resource: + + identities do + identity :active, [:status] do + where expr(status == "active") + end + end + + An appropriate `identity_wheres_to_sql` would need to be made to generate the + correct migration for the partial index used by the identity: + + postgres do + ... + + identity_wheres_to_sql active: "status = 'active'" + end + """ + @spec identity_wheres_to_sql(Ash.Resource.t()) :: keyword(String.t()) def identity_wheres_to_sql(resource) do Extension.get_opt(resource, [:postgres], :identity_wheres_to_sql, []) end + @doc """ + Returns the literal SQL for the `where` clause given a resource and an + identity name. + + See `identity_wheres_to_sql/1` for more details. + """ + @spec identity_where_to_sql(Ash.Resource.t(), atom()) :: String.t() | nil def identity_where_to_sql(resource, identity) do identity_wheres_to_sql(resource)[identity] end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 110725fc..062bf348 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3006,9 +3006,11 @@ defmodule AshPostgres.MigrationGenerator do where: if identity.where do AshPostgres.DataLayer.Info.identity_where_to_sql(resource, identity.name) || - raise( - "Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql`, or skip this identity with `postgres.skip_unique_indexes`" - ) + raise(""" + Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql`, or skip this identity with `postgres.skip_unique_indexes`. + + See https://hexdocs.pm/ash_postgres/AshPostgres.DataLayer.Info.html#identity_wheres_to_sql/1 for an example. + """) end } end) From 25fbdfc9ac3df82d3729a54ca920ef6f1a151a06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:52:49 -0500 Subject: [PATCH 0798/1215] chore(deps): bump ecto in the production-dependencies group (#436) Bumps the production-dependencies group with 1 update: [ecto](https://github.com/elixir-ecto/ecto). Updates `ecto` from 3.12.4 to 3.12.5 - [Release notes](https://github.com/elixir-ecto/ecto/releases) - [Changelog](https://github.com/elixir-ecto/ecto/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto/compare/v3.12.4...v3.12.5) --- updated-dependencies: - dependency-name: ecto dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c1d1d1e1..f18f8c17 100644 --- a/mix.lock +++ b/mix.lock @@ -9,7 +9,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, - "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, From 48b27031e1dfe8fef8a435f721708a8d8fd39bec Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Dec 2024 11:54:37 -0500 Subject: [PATCH 0799/1215] fix: don't attempt to use non-existent relationship --- lib/resource_generator/resource_generator.ex | 36 +++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index a4fbdeb4..fb7cdeae 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -362,22 +362,26 @@ defmodule AshPostgres.ResourceGenerator do Enum.find(table_spec.relationships, fn relationship -> relationship.type == :belongs_to and relationship.constraint_name == foreign_key.constraint_name - end).name - - options = - "" - |> add_on(:update, foreign_key.on_update) - |> add_on(:delete, foreign_key.on_delete) - |> add_match_with(foreign_key.match_with) - |> add_match_type(foreign_key.match_type) - - [ - """ - reference :#{relationship} do - #{options} - end - """ - ] + end) + + if relationship do + relationship = relationship.name + + options = + "" + |> add_on(:update, foreign_key.on_update) + |> add_on(:delete, foreign_key.on_delete) + |> add_match_with(foreign_key.match_with) + |> add_match_type(foreign_key.match_type) + + [ + """ + reference :#{relationship} do + #{options} + end + """ + ] + end end |> Enum.join("\n") |> String.trim() From 411a94e923f18feee4f955e7a08db58f19e13930 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Dec 2024 12:34:49 -0500 Subject: [PATCH 0800/1215] chore: add missing else case --- lib/resource_generator/resource_generator.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index fb7cdeae..9b884778 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -381,6 +381,8 @@ defmodule AshPostgres.ResourceGenerator do end """ ] + else + [] end end |> Enum.join("\n") From 14374adbb48e33c3761b64d61cc90fb4197be31f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Dec 2024 12:45:55 -0500 Subject: [PATCH 0801/1215] fix: split off varchar options from index --- lib/resource_generator/resource_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 9b884778..34bb5b96 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -452,7 +452,7 @@ defmodule AshPostgres.ResourceGenerator do if String.contains?(thing, "(") do inspect(thing) else - ":#{thing}" + Enum.at(String.split(":#{thing}", " "), 0) end end) From b2151d47d0caae9e42e6fc97c4a49cecf13415c0 Mon Sep 17 00:00:00 2001 From: Steve Brambilla Date: Thu, 5 Dec 2024 14:05:21 -0500 Subject: [PATCH 0802/1215] improvement: add postgres_reference_expr callback (#438) Custom types can use this to transform bare attribute expressions into boolean expressions for filters. --- lib/sql_implementation.ex | 27 +++++++++++++++++++++++++++ lib/type.ex | 11 ++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index b9a74f93..61cff0cf 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -153,6 +153,33 @@ defmodule AshPostgres.SqlImplementation do {:ok, Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)), acc} end + def expr( + query, + %Ash.Query.Ref{ + attribute: %Ash.Resource.Attribute{ + type: attr_type, + constraints: constraints, + }, + bare?: true + } = ref, + bindings, + embedded?, + acc, + type + ) do + if function_exported?(attr_type, :postgres_reference_expr, 3) do + non_bare_ref = %Ash.Query.Ref{ ref | bare?: nil } + {expr, acc} = AshSql.Expr.dynamic_expr(query, non_bare_ref, bindings, embedded?, type, acc) + + case attr_type.postgres_reference_expr(attr_type, constraints, expr) do + {:ok, bare_expr} -> {:ok, bare_expr, acc} + :error -> :error + end + else + :error + end + end + def expr( _query, _expr, diff --git a/lib/type.ex b/lib/type.ex index 4f6343d0..a437057e 100644 --- a/lib/type.ex +++ b/lib/type.ex @@ -8,12 +8,21 @@ defmodule AshPostgres.Type do @callback value_to_postgres_default(Ash.Type.t(), Ash.Type.constraints(), term) :: {:ok, String.t()} | :error + @callback postgres_reference_expr(Ash.Type.t(), Ash.Type.constraints(), term) :: + {:ok, term} | :error + + @optional_callbacks value_to_postgres_default: 3, + postgres_reference_expr: 3 + defmacro __using__(_) do quote do @behaviour AshPostgres.Type + def value_to_postgres_default(_, _, _), do: :error + def postgres_reference_expr(_, _, _), do: :error - defoverridable value_to_postgres_default: 3 + defoverridable value_to_postgres_default: 3, + postgres_reference_expr: 3 end end end From 6da0cf0357b78d1f8da66c07d7514073a00660a5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 6 Dec 2024 14:40:51 -0500 Subject: [PATCH 0803/1215] chore: release version v2.4.15 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 6 +++--- mix.lock | 6 +++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d7e4b9..71434a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.15](https://github.com/ash-project/ash_postgres/compare/v2.4.14...v2.4.15) (2024-12-06) + + + + +### Bug Fixes: + +* split off varchar options from index + +* don't attempt to use non-existent relationship + +* handle manual/no_attributes? relationships in lateral join logic + +* don't use `priv` configuration for snapshot_path + +### Improvements: + +* update sql implementation for type determination + ## [v2.4.14](https://github.com/ash-project/ash_postgres/compare/v2.4.13...v2.4.14) (2024-11-27) diff --git a/mix.exs b/mix.exs index 69599c3e..09a6a1b9 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.14" + @version "2.4.15" def project do [ @@ -164,8 +164,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.37")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.39")}, + {:ash, ash_version("~> 3.4 and >= 3.4.44")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.40")}, {:igniter, "~> 0.4 and >= 0.4.4"}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index f18f8c17..ef9171ac 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.43", "5ffd63e49b71ceaf7ed15efd20cd60ec28e9c55e28fca32b24dcc690644f2656", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b551208878c8e91f4ac3d3b6aee037f071a90a9c1151360f2a658e3b0aadf17"}, - "ash_sql": {:hex, :ash_sql, "0.2.39", "75411bea310ce514c16acd0855bdb9a754dba838fa566e1f053591fbd3da0dfb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "012904de597b9132b7dfdaf073ed8b0897f78ff9263f4036478cece4ac1fa97f"}, + "ash": {:hex, :ash, "3.4.44", "9001c3b7fe6fbacc610ef0ee76d554a4ecca21eab6e7740e546017032f50496b", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "477fcca10f01b9d8dbc6ae0e17afccfb9ce85e63ec08f5f3ec992e836f52c0c7"}, + "ash_sql": {:hex, :ash_sql, "0.2.40", "b44a2b4a90e6bd228e60f673bc325e0c38bb0d94c2f36edfb45fb7d9bb418794", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a547a79a4d8ba84c2d8bb40aafbeddb0ad936987452d1612379e9c5f60b62747"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, @@ -33,7 +33,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, - "reactor": {:hex, :reactor, "0.10.1", "7aad41c6f88c5214c5f878c597bc64d0f9983af3003bf2a6dbece031630a71b7", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b1aca34c0eafaefa98163778fc8252273638e36d0a70165b752910015161b6a8"}, + "reactor": {:hex, :reactor, "0.10.2", "a9150cbada58e5331c5250c51c6a8c2d7c4d337919fc71c7dc188a7ae5b6de89", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5c46e71153f540b95e1a240365fc534daead9d19abe0d46eb819b7d715663484"}, "rewrite": {:hex, :rewrite, "1.1.1", "0e6674eb5f8cb11aabe5ad6207151b4156bf173aa9b43133a68f8cc882364570", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "fcd688b3ca543c3a1f1f4615ccc054ec37cfcde91133a27a683ec09b35ae1496"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, From 4392ee1ae8faab0c2eae9a6cf6ed314c73187570 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 8 Dec 2024 23:34:51 -0500 Subject: [PATCH 0804/1215] docs: fix manual cte relationship example --- documentation/topics/advanced/manual-relationships.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index 47e90e54..62291adb 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -236,18 +236,18 @@ defmodule MyApp.Employee.ManagedEmployees do # https://stackoverflow.com/questions/39458572/ecto-declare-schema-for-a-query employee_keys = Employee.__schema__(:fields) - cte_name = + cte_name_ref = from(cte in fragment("?", literal(^cte_name)), select: map(cte, ^employee_keys)) recursion_query = query - |> join(:inner, [l], lt in ^cte_name, on: l.manager_id == lt.id) + |> join(:inner, [l], lt in ^cte_name_ref, on: l.manager_id == lt.id) descendants_query = immediate_parents |> union(^recursion_query) - cte_name + cte_name_ref |> recursive_ctes(true) |> with_cte(^cte_name, as: ^descendants_query) end From f8daf7f6733fb5408c8f0baf8bfdb88c500848a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Bergstr=C3=B6m?= Date: Mon, 9 Dec 2024 18:58:08 +0100 Subject: [PATCH 0805/1215] test: tests for relationships with `parent(join_resource)` references (#439) --- lib/sql_implementation.ex | 28 +-- .../co_authored_posts/20241208221219.json | 97 ++++++++ .../20241208221219_migrate_resources44.exs | 48 ++++ test/many_to_many_expr_test.exs | 218 ++++++++++++++++++ test/support/domain.ex | 1 + test/support/resources/author.ex | 35 +++ test/support/resources/co_authored_post.ex | 61 +++++ test/support/resources/post.ex | 18 ++ 8 files changed, 492 insertions(+), 14 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json create mode 100644 priv/test_repo/migrations/20241208221219_migrate_resources44.exs create mode 100644 test/many_to_many_expr_test.exs create mode 100644 test/support/resources/co_authored_post.ex diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 61cff0cf..15a50bfd 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -154,21 +154,21 @@ defmodule AshPostgres.SqlImplementation do end def expr( - query, - %Ash.Query.Ref{ - attribute: %Ash.Resource.Attribute{ - type: attr_type, - constraints: constraints, - }, - bare?: true - } = ref, - bindings, - embedded?, - acc, - type - ) do + query, + %Ash.Query.Ref{ + attribute: %Ash.Resource.Attribute{ + type: attr_type, + constraints: constraints + }, + bare?: true + } = ref, + bindings, + embedded?, + acc, + type + ) do if function_exported?(attr_type, :postgres_reference_expr, 3) do - non_bare_ref = %Ash.Query.Ref{ ref | bare?: nil } + non_bare_ref = %Ash.Query.Ref{ref | bare?: nil} {expr, acc} = AshSql.Expr.dynamic_expr(query, non_bare_ref, bindings, embedded?, type, acc) case attr_type.postgres_reference_expr(attr_type, constraints, expr) do diff --git a/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json b/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json new file mode 100644 index 00000000..4503d9d8 --- /dev/null +++ b/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json @@ -0,0 +1,97 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "was_cancelled_at", + "type": "utc_datetime" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "co_authored_posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "co_authored_posts_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "post_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "E1F2AC3AED1987928E3A2446584C268EC54D0BCA616D81A495F4AB26E3999444", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "co_authored_posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20241208221219_migrate_resources44.exs b/priv/test_repo/migrations/20241208221219_migrate_resources44.exs new file mode 100644 index 00000000..3665d579 --- /dev/null +++ b/priv/test_repo/migrations/20241208221219_migrate_resources44.exs @@ -0,0 +1,48 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources44 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 + create table(:co_authored_posts, primary_key: false) do + add(:role, :text, null: false) + add(:was_cancelled_at, :utc_datetime) + + add( + :author_id, + references(:authors, + column: :id, + name: "co_authored_posts_author_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + + add( + :post_id, + references(:posts, + column: :id, + name: "co_authored_posts_post_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + end + end + + def down do + drop(constraint(:co_authored_posts, "co_authored_posts_author_id_fkey")) + + drop(constraint(:co_authored_posts, "co_authored_posts_post_id_fkey")) + + drop(table(:co_authored_posts)) + end +end diff --git a/test/many_to_many_expr_test.exs b/test/many_to_many_expr_test.exs new file mode 100644 index 00000000..3c112143 --- /dev/null +++ b/test/many_to_many_expr_test.exs @@ -0,0 +1,218 @@ +defmodule AshPostgres.ManyToManyExprTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.Author + alias AshPostgres.Test.CoAuthorPost + alias AshPostgres.Test.Post + + require Ash.Query + + setup ctx do + main_author = + if ctx[:main_author?], + do: create_author(), + else: nil + + co_authors = + if ctx[:co_authors], + do: + 1..ctx[:co_authors] + |> Stream.map( + &(Author + |> Ash.Changeset.for_create(:create, %{first_name: "John #{&1}", last_name: "Doe"}) + |> Ash.create!()) + ) + |> Enum.into([]), + else: [] + + %{ + main_author: main_author, + co_authors: co_authors + } + end + + def create_author(params \\ %{first_name: "John", last_name: "Doe"}) do + Author + |> Ash.Changeset.for_create(:create, params) + |> Ash.create!() + end + + def create_post(author) do + Post + |> Ash.Changeset.for_create(:create, %{title: "Post by #{author.first_name}"}) + |> Ash.create!() + end + + def create_co_author_post(author, post, role) do + CoAuthorPost + |> Ash.Changeset.for_create(:create, %{author_id: author.id, post_id: post.id, role: role}) + |> Ash.create!() + end + + def get_author!(author_id) do + Author + |> Ash.Query.new() + |> Ash.Query.filter(id == ^author_id) + |> Ash.Query.load([ + :all_co_authored_posts, + :cancelled_co_authored_posts, + :editor_of, + :writer_of + ]) + |> Ash.read_one!() + end + + def get_co_author_post!(a_id, p_id) do + CoAuthorPost + |> Ash.Query.new() + |> Ash.Query.filter(author_id == ^a_id and post_id == ^p_id) + |> Ash.read_one!() + end + + def get_post!(post_id) do + Post.get_by_id!(post_id, load: [:co_author_posts, :co_authors_unfiltered, :co_authors]) + end + + def cancel(author, post) do + get_co_author_post!(author.id, post.id) + |> CoAuthorPost.cancel() + end + + def uncancel(author, post) do + get_co_author_post!(author.id, post.id) + |> CoAuthorPost.uncancel() + end + + describe "manual join-resource insertion" do + @tag main_author?: true + @tag co_authors: 3 + test "filter on many_to_many relationship using parent works as expected - basic", + %{ + main_author: main_author, + co_authors: co_authors + } do + post = create_post(main_author) + + [first_ca, second_ca, third_ca] = co_authors + + # Add first co-author + create_co_author_post(first_ca, post, :editor) + + first_ca = get_author!(first_ca.id) + post = get_post!(post.id) + + assert Enum.count(post.co_authors) == 1 + assert Enum.count(first_ca.all_co_authored_posts) == 1 + assert Enum.count(first_ca.editor_of) == 1 + assert Enum.empty?(first_ca.writer_of) == true + assert Enum.empty?(first_ca.cancelled_co_authored_posts) == true + + # Add second co-author + create_co_author_post(second_ca, post, :writer) + + second_ca = get_author!(second_ca.id) + post = get_post!(post.id) + + assert Enum.count(post.co_authors) == 2 + assert Enum.count(second_ca.all_co_authored_posts) == 1 + assert Enum.count(second_ca.writer_of) == 1 + assert Enum.empty?(second_ca.editor_of) == true + assert Enum.empty?(second_ca.cancelled_co_authored_posts) == true + + # Add third co-author + create_co_author_post(third_ca, post, :proof_reader) + + third_ca = get_author!(third_ca.id) + post = get_post!(post.id) + + assert Enum.count(post.co_authors) == 3 + assert Enum.count(third_ca.all_co_authored_posts) == 1 + assert Enum.empty?(third_ca.editor_of) == true + assert Enum.empty?(third_ca.writer_of) == true + assert Enum.empty?(third_ca.cancelled_co_authored_posts) == true + end + + @tag main_author?: true + @tag co_authors: 4 + test "filter on many_to_many relationship using parent works as expected - cancelled", + %{ + main_author: main_author, + co_authors: co_authors + } do + first_post = create_post(main_author) + second_post = create_post(main_author) + + [first_ca, second_ca, third_ca, fourth_ca] = co_authors + + # Add first co-author + create_co_author_post(first_ca, first_post, :editor) + create_co_author_post(first_ca, second_post, :writer) + + first_ca = get_author!(first_ca.id) + first_post = get_post!(first_post.id) + + assert Enum.count(first_post.co_authors) == 1 + assert Enum.count(first_post.co_authors_unfiltered) == 1 + + assert Enum.count(first_ca.all_co_authored_posts) == 2 + assert Enum.count(first_ca.editor_of) == 1 + assert Enum.count(first_ca.writer_of) == 1 + assert Enum.empty?(first_ca.cancelled_co_authored_posts) == true + + # Add second co-author + create_co_author_post(second_ca, first_post, :proof_reader) + create_co_author_post(second_ca, second_post, :writer) + + second_ca = get_author!(second_ca.id) + first_post = get_post!(first_post.id) + second_post = get_post!(second_post.id) + + assert Enum.count(second_post.co_authors) == 2 + assert Enum.count(second_post.co_authors_unfiltered) == 2 + + assert Enum.count(second_ca.all_co_authored_posts) == 2 + assert Enum.count(second_ca.writer_of) == 1 + assert Enum.empty?(second_ca.editor_of) == true + assert Enum.empty?(second_ca.cancelled_co_authored_posts) == true + + # Add third co-author + create_co_author_post(third_ca, first_post, :proof_reader) + create_co_author_post(third_ca, second_post, :proof_reader) + cancel(third_ca, second_post) + + third_ca = get_author!(third_ca.id) + first_post = get_post!(first_post.id) + second_post = get_post!(second_post.id) + + assert Enum.count(first_post.co_authors) == 3 + assert Enum.count(first_post.co_authors_unfiltered) == 3 + assert Enum.count(second_post.co_authors) == 2 + assert Enum.count(second_post.co_authors_unfiltered) == 3 + + assert Enum.count(third_ca.all_co_authored_posts) == 2 + assert Enum.count(third_ca.cancelled_co_authored_posts) == 1 + assert Enum.empty?(third_ca.editor_of) == true + assert Enum.empty?(third_ca.writer_of) == true + + # Add fourth co-author + create_co_author_post(fourth_ca, first_post, :proof_reader) + create_co_author_post(fourth_ca, second_post, :editor) + cancel(fourth_ca, first_post) + cancel(fourth_ca, second_post) + + fourth_ca = get_author!(fourth_ca.id) + first_post = get_post!(first_post.id) + second_post = get_post!(second_post.id) + + assert Enum.count(first_post.co_authors) == 3 + assert Enum.count(first_post.co_authors_unfiltered) == 4 + assert Enum.count(second_post.co_authors) == 2 + assert Enum.count(second_post.co_authors_unfiltered) == 4 + + assert Enum.count(fourth_ca.all_co_authored_posts) == 2 + assert Enum.count(fourth_ca.editor_of) == 1 + assert Enum.count(fourth_ca.cancelled_co_authored_posts) == 2 + assert Enum.empty?(fourth_ca.writer_of) == true + end + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index a571efb9..60d2275a 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -3,6 +3,7 @@ defmodule AshPostgres.Test.Domain do use Ash.Domain resources do + resource(AshPostgres.Test.CoAuthorPost) resource(AshPostgres.Test.Post) resource(AshPostgres.Test.Comment) resource(AshPostgres.Test.IntegerPost) diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index dd358005..d3beca51 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -53,6 +53,41 @@ defmodule AshPostgres.Test.Author do destination_attribute(:first_name) filter(expr(parent(id) != id)) end + + has_many :credited_posts, AshPostgres.Test.CoAuthorPost do + public?(true) + + destination_attribute(:author_id) + end + + many_to_many :all_co_authored_posts, AshPostgres.Test.Post do + public?(true) + join_relationship(:credited_posts) + source_attribute_on_join_resource(:author_id) + destination_attribute_on_join_resource(:post_id) + end + + many_to_many :writer_of, AshPostgres.Test.Post do + public?(true) + join_relationship(:credited_posts) + source_attribute_on_join_resource(:author_id) + destination_attribute_on_join_resource(:post_id) + filter(expr(parent(credited_posts.role) == :writer)) + end + + many_to_many :editor_of, AshPostgres.Test.Post do + public?(true) + join_relationship(:credited_posts) + source_attribute_on_join_resource(:author_id) + destination_attribute_on_join_resource(:post_id) + filter(expr(parent(credited_posts.role) == :editor)) + end + + many_to_many :cancelled_co_authored_posts, AshPostgres.Test.Post do + public?(true) + join_relationship(:credited_posts) + filter(expr(not is_nil(parent(credited_posts.was_cancelled_at)))) + end end aggregates do diff --git a/test/support/resources/co_authored_post.ex b/test/support/resources/co_authored_post.ex new file mode 100644 index 00000000..5519144b --- /dev/null +++ b/test/support/resources/co_authored_post.ex @@ -0,0 +1,61 @@ +defmodule AshPostgres.Test.CoAuthorPost do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "co_authored_posts" + repo AshPostgres.TestRepo + end + + attributes do + attribute :role, :atom do + allow_nil?(false) + public?(true) + + constraints(one_of: [:editor, :writer, :proof_reader]) + end + + attribute :was_cancelled_at, :datetime do + allow_nil?(true) + public?(true) + end + end + + actions do + default_accept(:*) + + defaults([:read, :update, :destroy]) + + create :create do + end + + update :cancel_author do + change(set_attribute(:was_cancelled_at, DateTime.utc_now())) + end + + update :uncancel_author do + change(set_attribute(:was_cancelled_at, nil)) + end + end + + code_interface do + define(:cancel, action: :cancel_author) + define(:uncancel, action: :uncancel_author) + end + + relationships do + belongs_to :author, AshPostgres.Test.Author do + primary_key?(true) + public?(true) + allow_nil?(false) + end + + belongs_to :post, AshPostgres.Test.Post do + primary_key?(true) + public?(true) + allow_nil?(false) + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index db11f897..92b6e4ab 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -448,6 +448,24 @@ defmodule AshPostgres.Test.Post do public?(true) end + has_many :co_author_posts, AshPostgres.Test.CoAuthorPost do + public?(true) + + destination_attribute(:post_id) + end + + many_to_many :co_authors, AshPostgres.Test.Author do + public?(true) + join_relationship(:co_author_posts) + + filter(expr(is_nil(parent(co_author_posts.was_cancelled_at)))) + end + + many_to_many :co_authors_unfiltered, AshPostgres.Test.Author do + public?(true) + join_relationship(:co_author_posts) + end + has_many :posts_with_matching_title, __MODULE__ do public?(true) no_attributes?(true) From 5d9f0fc439b0e691b8bd7abb5daf4f8ddd26d1ea Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Dec 2024 10:54:02 -0500 Subject: [PATCH 0806/1215] fix: only build references for belongs_to relationships --- lib/migration_generator/migration_generator.ex | 7 +++++-- mix.lock | 2 +- test/many_to_many_expr_test.exs | 8 ++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 062bf348..6c58be70 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2852,7 +2852,10 @@ defmodule AshPostgres.MigrationGenerator do end defp find_reference(resource, table, attribute) do - Enum.find_value(Ash.Resource.Info.relationships(resource), fn relationship -> + resource + |> Ash.Resource.Info.relationships() + |> Enum.filter(&(&1.type == :belongs_to)) + |> Enum.find_value(fn relationship -> source_attribute_name = relationship.source |> Ash.Resource.Info.attribute(relationship.source_attribute) @@ -2860,7 +2863,7 @@ defmodule AshPostgres.MigrationGenerator do attribute.source || attribute.name end) - if attribute.source == source_attribute_name && relationship.type == :belongs_to && + if attribute.source == source_attribute_name && foreign_key?(relationship) do configured_reference = configured_reference(resource, table, attribute.source || attribute.name, relationship) diff --git a/mix.lock b/mix.lock index ef9171ac..8d218f70 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.44", "9001c3b7fe6fbacc610ef0ee76d554a4ecca21eab6e7740e546017032f50496b", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "477fcca10f01b9d8dbc6ae0e17afccfb9ce85e63ec08f5f3ec992e836f52c0c7"}, + "ash": {:hex, :ash, "3.4.44", "10f7d0952324e4e618fd79ec6c4a4df88a087342244937c1d7807e424db05242", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c4d51c03b57b70ab77d782d997ab0b534415789af2fd05190e51eba2e53daee5"}, "ash_sql": {:hex, :ash_sql, "0.2.40", "b44a2b4a90e6bd228e60f673bc325e0c38bb0d94c2f36edfb45fb7d9bb418794", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a547a79a4d8ba84c2d8bb40aafbeddb0ad936987452d1612379e9c5f60b62747"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/many_to_many_expr_test.exs b/test/many_to_many_expr_test.exs index 3c112143..a19fe509 100644 --- a/test/many_to_many_expr_test.exs +++ b/test/many_to_many_expr_test.exs @@ -84,6 +84,14 @@ defmodule AshPostgres.ManyToManyExprTest do end describe "manual join-resource insertion" do + @tag main_author?: true + @tag co_authors: 3 + test "using exists does not raise an error" do + Post + |> Ash.Query.filter(exists(co_authors, true)) + |> Ash.read!() + end + @tag main_author?: true @tag co_authors: 3 test "filter on many_to_many relationship using parent works as expected - basic", From 877ad289a7dae7ddd6ce570fed5ac990a5f98031 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Dec 2024 10:34:55 -0500 Subject: [PATCH 0807/1215] fix: properly support expr errors in bulk create --- lib/data_layer.ex | 61 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 670567ba..c1415814 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1551,7 +1551,7 @@ defmodule AshPostgres.DataLayer do true -> {:ok, Ecto.Query.exclude(root_query, :order_by), - root_query.__ash_bindings__.expression_accumulator, false} + Map.get(root_query, :__ash_bindings__).expression_accumulator, false} end case root_query_result do @@ -1966,7 +1966,7 @@ defmodule AshPostgres.DataLayer do fields_to_upsert = upsert_fields -- - Keyword.keys(Enum.at(changesets, 0).atomics) -- keys + (Keyword.keys(Enum.at(changesets, 0).atomics) -- keys) fields_to_upsert |> Enum.uniq() @@ -2184,33 +2184,6 @@ defmodule AshPostgres.DataLayer do ) end - defp handle_raised_error( - %Postgrex.Error{} = error, - stacktrace, - {:bulk_create, fake_changeset}, - _resource - ) do - case Ecto.Adapters.Postgres.Connection.to_constraints(error, []) do - [] -> - {:error, Ash.Error.to_ash_error(error, stacktrace)} - - constraints -> - {:error, - fake_changeset - |> constraints_to_errors(:insert, constraints) - |> Ash.Error.to_ash_error()} - end - end - - defp handle_raised_error(%Ecto.Query.CastError{} = e, stacktrace, context, resource) do - handle_raised_error( - Ash.Error.Query.InvalidFilterValue.exception(value: e.value, context: context), - stacktrace, - context, - resource - ) - end - defp handle_raised_error( %Postgrex.Error{ postgres: %{ @@ -2254,6 +2227,33 @@ defmodule AshPostgres.DataLayer do {:error, :no_rollback, Ash.Error.from_json(exception, input)} end + defp handle_raised_error( + %Postgrex.Error{} = error, + stacktrace, + {:bulk_create, fake_changeset}, + _resource + ) do + case Ecto.Adapters.Postgres.Connection.to_constraints(error, []) do + [] -> + {:error, Ash.Error.to_ash_error(error, stacktrace)} + + constraints -> + {:error, + fake_changeset + |> constraints_to_errors(:insert, constraints) + |> Ash.Error.to_ash_error()} + end + end + + defp handle_raised_error(%Ecto.Query.CastError{} = e, stacktrace, context, resource) do + handle_raised_error( + Ash.Error.Query.InvalidFilterValue.exception(value: e.value, context: context), + stacktrace, + context, + resource + ) + end + defp handle_raised_error( %Postgrex.Error{} = error, stacktrace, @@ -2657,6 +2657,9 @@ defmodule AshPostgres.DataLayer do {:ok, [result]} -> {:ok, result} + {:error, :no_rollback, error} -> + {:error, :no_rollback, error} + {:error, error} -> {:error, error} end From 2ddcbb549ec0f6c7724f471ef7adb0c7d54ea24b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Dec 2024 10:35:01 -0500 Subject: [PATCH 0808/1215] chore: release version v2.4.16 --- .tool-versions | 2 +- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index ce0360e0..307762be 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 27.0.1 -elixir 1.17.2-otp-27 +elixir 1.18.0-rc.0-otp-27 diff --git a/CHANGELOG.md b/CHANGELOG.md index 71434a9c..88bc29da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.16](https://github.com/ash-project/ash_postgres/compare/v2.4.15...v2.4.16) (2024-12-12) + + + + +### Bug Fixes: + +* properly support expr errors in bulk create + +* only build references for belongs_to relationships + +### Improvements: + +* add postgres_reference_expr callback (#438) + ## [v2.4.15](https://github.com/ash-project/ash_postgres/compare/v2.4.14...v2.4.15) (2024-12-06) diff --git a/mix.exs b/mix.exs index 09a6a1b9..36f287c6 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.15" + @version "2.4.16" def project do [ From aab226c8e5ef790a640bfca125b7985f7567e7f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:28:37 -0500 Subject: [PATCH 0809/1215] chore(deps): bump ash in the production-dependencies group (#441) Bumps the production-dependencies group with 1 update: [ash](https://github.com/ash-project/ash). Updates `ash` from 3.4.44 to 3.4.45 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.44...v3.4.45) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8d218f70..26d3753a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.44", "10f7d0952324e4e618fd79ec6c4a4df88a087342244937c1d7807e424db05242", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c4d51c03b57b70ab77d782d997ab0b534415789af2fd05190e51eba2e53daee5"}, + "ash": {:hex, :ash, "3.4.45", "d52f175f213c7d76be40916ed84f26f587c89b396025cc915cda2e9f569dc0f7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9f44536e9dcb852a63abf08c1f334004093d6ad9d6f927e614ffed066bac4fd"}, "ash_sql": {:hex, :ash_sql, "0.2.40", "b44a2b4a90e6bd228e60f673bc325e0c38bb0d94c2f36edfb45fb7d9bb418794", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a547a79a4d8ba84c2d8bb40aafbeddb0ad936987452d1612379e9c5f60b62747"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 122bcf50e10c55b61519f69a5be5397847eae27f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Dec 2024 18:09:22 -0500 Subject: [PATCH 0810/1215] chore: fix tenant set --- test/multitenancy_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index bacd210a..0c3326b9 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -53,7 +53,7 @@ defmodule AshPostgres.Test.MultitenancyTest do assert [] = Org - |> Ash.Query.set_tenant(org1) + |> Ash.Query.set_tenant("org_" <> org1.id) |> Ash.Query.for_read(:has_policies, %{}, actor: user, authorize?: true) |> Ash.read!() end From 0b11d4379146d8eee9c5e40cab9815c2a2f91468 Mon Sep 17 00:00:00 2001 From: Oliver Severin Mulelid-Tynes Date: Fri, 13 Dec 2024 18:35:41 +0100 Subject: [PATCH 0811/1215] fix: alter resource generation query to go to the source pg_constraints table instead of to the view to fetch constraint data (#443) --- lib/resource_generator/spec.ex | 75 +++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index d3a256d1..3142573f 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -123,35 +123,52 @@ defmodule AshPostgres.ResourceGenerator.Spec do %Postgrex.Result{rows: fkey_rows} = spec.repo.query!( """ - SELECT - tc.constraint_name, - rc.match_option AS match_type, - rc.update_rule AS on_update, - rc.delete_rule AS on_delete, - array_agg(DISTINCT kcu.column_name) AS referencing_columns, - array_agg(DISTINCT ccu.column_name) AS referenced_columns, - ccu.table_name AS foreign_table_name - FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - AND ccu.table_schema = tc.table_schema - JOIN information_schema.referential_constraints AS rc - ON tc.constraint_name = rc.constraint_name - AND tc.table_schema = rc.constraint_schema - WHERE - tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = $1 - AND tc.table_schema = $2 - GROUP BY - tc.constraint_name, - ccu.table_name, - rc.match_option, - rc.update_rule, - rc.delete_rule; + SELECT tc.constraint_name, + -- This has to go via the pg_constraints table directly + -- because the built in constraint view does not surface the table name + -- and constraint names are only unique per table + CASE pgc.confmatchtype + WHEN 'f'::"char" THEN 'FULL'::text + WHEN 'p'::"char" THEN 'PARTIAL'::text + WHEN 's'::"char" THEN 'NONE'::text + ELSE NULL::text + END AS match_option, + CASE pgc.confupdtype + WHEN 'c'::"char" THEN 'CASCADE'::text + WHEN 'n'::"char" THEN 'SET NULL'::text + WHEN 'd'::"char" THEN 'SET DEFAULT'::text + WHEN 'r'::"char" THEN 'RESTRICT'::text + WHEN 'a'::"char" THEN 'NO ACTION'::text + ELSE NULL::text + END AS update_rule, + CASE pgc.confdeltype + WHEN 'c'::"char" THEN 'CASCADE'::text + WHEN 'n'::"char" THEN 'SET NULL'::text + WHEN 'd'::"char" THEN 'SET DEFAULT'::text + WHEN 'r'::"char" THEN 'RESTRICT'::text + WHEN 'a'::"char" THEN 'NO ACTION'::text + ELSE NULL::text + END AS delete_rule, + array_agg(DISTINCT kcu.column_name) AS referencing_columns, + array_agg(DISTINCT ccu.column_name) AS referenced_columns, + ccu.table_name AS foreign_table_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + AND ccu.table_schema = tc.table_schema + JOIN pg_constraint AS pgc ON contype = 'f' AND conrelid = tc.table_name::regclass + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = $1 + AND tc.table_schema = $2 + GROUP BY tc.constraint_name, + ccu.table_name, + pgc.confmatchtype, + pgc.confupdtype, + pgc.confdeltype + """, [spec.table_name, spec.schema], log: false From e0d810eb4508e10e27e96b3558b9e2da847c8173 Mon Sep 17 00:00:00 2001 From: Oliver Severin Mulelid-Tynes Date: Sun, 15 Dec 2024 17:48:47 +0100 Subject: [PATCH 0812/1215] fix: Fix query for metadata on foreign keys and fix duplicate references being produced (#444) --- lib/resource_generator/resource_generator.ex | 22 ++--- lib/resource_generator/spec.ex | 87 +++++++++++--------- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 34bb5b96..abc70d5b 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -385,18 +385,18 @@ defmodule AshPostgres.ResourceGenerator do [] end end - |> Enum.join("\n") - |> String.trim() - |> then( - &[ - """ - references do - #{&1} - end - """ - ] - ) end) + |> Enum.join("\n") + |> String.trim() + |> then( + &[ + """ + references do + #{&1} + end + """ + ] + ) end defp add_match_with(str, empty) when empty in [[], nil], do: str diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 3142573f..a813e600 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -123,52 +123,57 @@ defmodule AshPostgres.ResourceGenerator.Spec do %Postgrex.Result{rows: fkey_rows} = spec.repo.query!( """ - SELECT tc.constraint_name, - -- This has to go via the pg_constraints table directly - -- because the built in constraint view does not surface the table name - -- and constraint names are only unique per table - CASE pgc.confmatchtype - WHEN 'f'::"char" THEN 'FULL'::text - WHEN 'p'::"char" THEN 'PARTIAL'::text - WHEN 's'::"char" THEN 'NONE'::text - ELSE NULL::text - END AS match_option, - CASE pgc.confupdtype - WHEN 'c'::"char" THEN 'CASCADE'::text - WHEN 'n'::"char" THEN 'SET NULL'::text - WHEN 'd'::"char" THEN 'SET DEFAULT'::text - WHEN 'r'::"char" THEN 'RESTRICT'::text - WHEN 'a'::"char" THEN 'NO ACTION'::text - ELSE NULL::text - END AS update_rule, - CASE pgc.confdeltype - WHEN 'c'::"char" THEN 'CASCADE'::text - WHEN 'n'::"char" THEN 'SET NULL'::text - WHEN 'd'::"char" THEN 'SET DEFAULT'::text - WHEN 'r'::"char" THEN 'RESTRICT'::text - WHEN 'a'::"char" THEN 'NO ACTION'::text - ELSE NULL::text - END AS delete_rule, + -- This has to go via the pg_constraints table directly + -- because the built in constraint view does not surface the table name + -- and constraint names are only unique per table + WITH constraints AS (SELECT conname as constraint_name, + ns.nspname::information_schema.sql_identifier AS table_schema, + CASE pgc.confmatchtype + WHEN 'f'::"char" THEN 'FULL'::text + WHEN 'p'::"char" THEN 'PARTIAL'::text + WHEN 's'::"char" THEN 'NONE'::text + ELSE NULL::text + END AS match_option, + CASE pgc.confupdtype + WHEN 'c'::"char" THEN 'CASCADE'::text + WHEN 'n'::"char" THEN 'SET NULL'::text + WHEN 'd'::"char" THEN 'SET DEFAULT'::text + WHEN 'r'::"char" THEN 'RESTRICT'::text + WHEN 'a'::"char" THEN 'NO ACTION'::text + ELSE NULL::text + END AS update_rule, + CASE pgc.confdeltype + WHEN 'c'::"char" THEN 'CASCADE'::text + WHEN 'n'::"char" THEN 'SET NULL'::text + WHEN 'd'::"char" THEN 'SET DEFAULT'::text + WHEN 'r'::"char" THEN 'RESTRICT'::text + WHEN 'a'::"char" THEN 'NO ACTION'::text + ELSE NULL::text + END AS delete_rule + FROM pg_constraint AS pgc + INNER JOIN pg_namespace AS ns ON pgc.connamespace = ns.oid + WHERE pgc.contype = 'f' -- Foreign key + AND pgc.conrelid = $1::text::regclass + AND ns.nspname = $2) + SELECT constraints.constraint_name, + constraints.match_option, + constraints.update_rule, + constraints.delete_rule, array_agg(DISTINCT kcu.column_name) AS referencing_columns, array_agg(DISTINCT ccu.column_name) AS referenced_columns, ccu.table_name AS foreign_table_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema + FROM information_schema.key_column_usage AS kcu + JOIN constraints + ON constraints.constraint_name = kcu.constraint_name + AND constraints.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - AND ccu.table_schema = tc.table_schema - JOIN pg_constraint AS pgc ON contype = 'f' AND conrelid = tc.table_name::regclass - WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = $1 - AND tc.table_schema = $2 - GROUP BY tc.constraint_name, + ON ccu.constraint_name = constraints.constraint_name + AND ccu.table_schema = constraints.table_schema + GROUP BY constraints.constraint_name, ccu.table_name, - pgc.confmatchtype, - pgc.confupdtype, - pgc.confdeltype - + constraints.match_option, + constraints.update_rule, + constraints.delete_rule """, [spec.table_name, spec.schema], log: false From d836fa80deb19e748d7f743fbde4435c9820f49e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 15 Dec 2024 11:55:16 -0500 Subject: [PATCH 0813/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 26d3753a..bd21dc9a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,11 +1,11 @@ %{ - "ash": {:hex, :ash, "3.4.45", "d52f175f213c7d76be40916ed84f26f587c89b396025cc915cda2e9f569dc0f7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9f44536e9dcb852a63abf08c1f334004093d6ad9d6f927e614ffed066bac4fd"}, - "ash_sql": {:hex, :ash_sql, "0.2.40", "b44a2b4a90e6bd228e60f673bc325e0c38bb0d94c2f36edfb45fb7d9bb418794", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a547a79a4d8ba84c2d8bb40aafbeddb0ad936987452d1612379e9c5f60b62747"}, + "ash": {:hex, :ash, "3.4.46", "24286834d87719a8d9e0d1addf4b5be4c2acca30c554dbd5d66229d04748a15d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "78cd7d1d3ef27516f88a503e181c8e050f80d93222d76696d7491b200bd606db"}, + "ash_sql": {:hex, :ash_sql, "0.2.41", "9e0a1686dc67a7cdc8435ced6c998dcd4de87980dde0a72d77946bbc9d1e65cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "226470dc8eeb3e89f98c0fb4ef11edf1b114e1caf3cd3457af7f9481df8221e8"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, - "decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, From 5b399543648b19c22a301948194a57c2407dd29e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Dec 2024 10:04:24 -0500 Subject: [PATCH 0814/1215] chore: format & small fix for resource generator --- lib/data_layer.ex | 2 +- lib/resource_generator/resource_generator.ex | 29 ++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c1415814..19d4452c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1966,7 +1966,7 @@ defmodule AshPostgres.DataLayer do fields_to_upsert = upsert_fields -- - (Keyword.keys(Enum.at(changesets, 0).atomics) -- keys) + Keyword.keys(Enum.at(changesets, 0).atomics) -- keys fields_to_upsert |> Enum.uniq() diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index abc70d5b..13ced059 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -386,17 +386,24 @@ defmodule AshPostgres.ResourceGenerator do end end end) - |> Enum.join("\n") - |> String.trim() - |> then( - &[ - """ - references do - #{&1} - end - """ - ] - ) + |> case do + [] -> + [] + + refs -> + refs + |> Enum.join("\n") + |> String.trim() + |> then( + &[ + """ + references do + #{&1} + end + """ + ] + ) + end end defp add_match_with(str, empty) when empty in [[], nil], do: str From 6126ef99a07304e00730d48234746e4408f5a5a9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Dec 2024 10:04:48 -0500 Subject: [PATCH 0815/1215] chore: release version v2.4.17 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88bc29da..af41d627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.17](https://github.com/ash-project/ash_postgres/compare/v2.4.16...v2.4.17) (2024-12-16) + + + + +### Bug Fixes: + +* Fix query for metadata on foreign keys and fix duplicate references being produced (#444) + +* alter resource generation query to go to the source pg_constraints table instead of to the view to fetch constraint data (#443) + ## [v2.4.16](https://github.com/ash-project/ash_postgres/compare/v2.4.15...v2.4.16) (2024-12-12) diff --git a/mix.exs b/mix.exs index 36f287c6..e94bd4b7 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.16" + @version "2.4.17" def project do [ From 25882e936aad8d00c5fa08181baba51d2c23c769 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 16 Dec 2024 11:30:40 -0500 Subject: [PATCH 0816/1215] test: add test showing no query with `exists` --- test/ash_postgres_test.exs | 27 +++++++++++++++++++++++++++ test/support/resources/post.ex | 9 +++++++++ 2 files changed, 36 insertions(+) diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 346037ad..353d3cdb 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -1,5 +1,6 @@ defmodule AshPostgresTest do use AshPostgres.RepoCase, async: false + import ExUnit.CaptureLog test "transaction metadata is given to on_transaction_begin" do AshPostgres.Test.Post @@ -40,4 +41,30 @@ defmodule AshPostgresTest do |> Map.get(:title) end end + + test "it does not run queries for exists/2 expressions that can be determined from loaded data" do + author = + AshPostgres.Test.Author + |> Ash.Changeset.for_create(:create, %{}, authorize?: false) + |> Ash.create!() + + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "good", author_id: author.id}) + |> Ash.create!() + |> Ash.load!(:author) + + log = + capture_log(fn -> + post + |> Ash.Changeset.for_update(:update_if_author, %{title: "bad"}, + authorize?: true, + actor: nil, + actor: author + ) + |> then(&AshPostgres.Test.Post.can_update_if_author?(author, &1)) + end) + + assert log == "" + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 92b6e4ab..a3fe41b3 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -80,6 +80,10 @@ defmodule AshPostgres.Test.Post do authorize_if(PassIfOriginalDataPresent) end + bypass action(:update_if_author) do + authorize_if relates_to_actor_via(:author) + end + policy action_type(:update) do authorize_if(action(:requires_initial_data)) authorize_if(relates_to_actor_via([:author, :authors_with_same_first_name])) @@ -231,6 +235,10 @@ defmodule AshPostgres.Test.Post do require_atomic?(false) end + update :update_if_author do + require_atomic?(false) + end + update(:dont_validate) update :change_title_to_foo_unless_its_already_foo do @@ -423,6 +431,7 @@ defmodule AshPostgres.Test.Post do define(:get_by_id, action: :read, get_by: [:id]) define(:increment_score, args: [{:optional, :amount}]) define(:destroy) + define(:update_if_author) define(:update_constrained_int, args: [:amount]) define_calculation(:upper_title, args: [:title]) From ff1e10efe85b25554bd09c6be14beb1f073b3c05 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Tue, 17 Dec 2024 16:55:26 -0600 Subject: [PATCH 0817/1215] docs: fix mention of `dont-drop-columns` option (#445) --- lib/mix/tasks/ash_postgres.generate_migrations.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index b71568d4..ea2c7383 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -9,7 +9,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do * `migration-path` - a custom path to store the migrations, defaults to "priv/repo_name/migrations". Migrations are stored in a folder for each repo, so `priv/repo_name/migrations` * `tenant-migration-path` - Same as `migration_path`, except for tenant-specific migrations - * `drop-columns` - whether or not to drop columns as attributes are removed. See below for more + * `dont-drop-columns` - whether or not to drop columns as attributes are removed. See below for more * `name` - names the generated migrations, prepending with the timestamp. The default is `migrate_resources_`, where `` is the count of migrations matching `*migrate_resources*` plus one. From db941ccff31f0802753574ff3fda7b6cdce4ee52 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Dec 2024 13:00:22 -0500 Subject: [PATCH 0818/1215] improvement: make tsvector type selectable --- lib/types/tsvector.ex | 55 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/types/tsvector.ex b/lib/types/tsvector.ex index a00705ec..f753b2b1 100644 --- a/lib/types/tsvector.ex +++ b/lib/types/tsvector.ex @@ -1,12 +1,63 @@ defmodule AshPostgres.Tsvector do @moduledoc """ - A thin wrapper around `:string` for working with tsvector types in calculations. + A type for representing postgres' tsvectors. - A calculation of this type cannot be selected, but may be used in calculations. + Values will be a list of `Postgrex.Lexeme` """ use Ash.Type.NewType, subtype_of: :term @impl true def storage_type(_), do: :tsvector + + @impl true + def cast_input(nil, _) do + {:ok, nil} + end + + def cast_input(values, _) when is_list(values) do + if Enum.all?(values, &is_struct(&1, Postgrex.Lexeme)) do + {:ok, values} + else + :error + end + end + + def cast_input(_, _) do + :error + end + + @impl true + def dump_to_native(nil, _) do + {:ok, nil} + end + + def dump_to_native(values, _) when is_list(values) do + if Enum.all?(values, &is_struct(&1, Postgrex.Lexeme)) do + {:ok, values} + else + :error + end + end + + def dump_to_native(_, _) do + :error + end + + @impl true + def cast_stored(nil, _) do + {:ok, nil} + end + + def cast_stored(values, _) when is_list(values) do + if Enum.all?(values, &is_struct(&1, Postgrex.Lexeme)) do + {:ok, values} + else + :error + end + end + + def cast_stored(_, _) do + :error + end end From a5e3eb7c47f6c9b22fbf730592d3e0375575f248 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Dec 2024 22:58:40 -0500 Subject: [PATCH 0819/1215] improvement: make igniter optional --- lib/data_layer.ex | 46 +- lib/igniter.ex | 276 ++--- lib/mix/tasks/ash_postgres.gen.resources.ex | 324 +++--- lib/mix/tasks/ash_postgres.install.ex | 814 ++++++------- lib/resource_generator/resource_generator.ex | 1079 +++++++++--------- mix.exs | 2 +- test/ash_postgres_test.exs | 4 +- test/support/resources/post.ex | 2 +- 8 files changed, 1304 insertions(+), 1243 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 19d4452c..480f644f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -408,8 +408,6 @@ defmodule AshPostgres.DataLayer do A postgres data layer that leverages Ecto's postgres capabilities. """ - require Igniter.Code.Common - use Spark.Dsl.Extension, sections: @sections, verifiers: [ @@ -1966,7 +1964,7 @@ defmodule AshPostgres.DataLayer do fields_to_upsert = upsert_fields -- - Keyword.keys(Enum.at(changesets, 0).atomics) -- keys + (Keyword.keys(Enum.at(changesets, 0).atomics) -- keys) fields_to_upsert |> Enum.uniq() @@ -3067,31 +3065,33 @@ defmodule AshPostgres.DataLayer do end end - def install(igniter, module, Ash.Resource, _path, argv) do - table_name = - module - |> Module.split() - |> List.last() - |> Macro.underscore() - |> Inflex.pluralize() + if Code.ensure_loaded?(Igniter) do + def install(igniter, module, Ash.Resource, _path, argv) do + table_name = + module + |> Module.split() + |> List.last() + |> Macro.underscore() + |> Inflex.pluralize() - {options, _, _} = OptionParser.parse(argv, switches: [repo: :string]) + {options, _, _} = OptionParser.parse(argv, switches: [repo: :string]) - repo = - case options[:repo] do - nil -> - Igniter.Project.Module.module_name(igniter, "Repo") + repo = + case options[:repo] do + nil -> + Igniter.Project.Module.module_name(igniter, "Repo") - repo -> - Igniter.Project.Module.parse(repo) - end + repo -> + Igniter.Project.Module.parse(repo) + end - igniter - |> Spark.Igniter.set_option(module, [:postgres, :table], table_name) - |> Spark.Igniter.set_option(module, [:postgres, :repo], repo) - end + igniter + |> Spark.Igniter.set_option(module, [:postgres, :table], table_name) + |> Spark.Igniter.set_option(module, [:postgres, :repo], repo) + end - def install(igniter, _, _, _), do: igniter + def install(igniter, _, _, _), do: igniter + end @impl true def rollback(resource, term) do diff --git a/lib/igniter.ex b/lib/igniter.ex index e4ddd6fb..75b636e6 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -1,170 +1,172 @@ -defmodule AshPostgres.Igniter do - @moduledoc "Codemods and utilities for working with AshPostgres & Igniter" +if Code.ensure_loaded?(Igniter) do + defmodule AshPostgres.Igniter do + @moduledoc "Codemods and utilities for working with AshPostgres & Igniter" - @doc false - def default_repo_contents(otp_app, name, opts \\ []) do - min_pg_version = get_min_pg_version(name, opts) + @doc false + def default_repo_contents(otp_app, name, opts \\ []) do + min_pg_version = get_min_pg_version(name, opts) - """ - use AshPostgres.Repo, otp_app: #{inspect(otp_app)} + """ + use AshPostgres.Repo, otp_app: #{inspect(otp_app)} - def min_pg_version do - %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} - end + def min_pg_version do + %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} + end - # Don't open unnecessary transactions - # will default to `false` in 4.0 - def prefer_transaction? do - false - end + # Don't open unnecessary transactions + # will default to `false` in 4.0 + def prefer_transaction? do + false + end - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """ end - """ - end - def table(igniter, resource) do - igniter - |> Spark.Igniter.get_option(resource, [:postgres, :table]) - |> case do - {igniter, {:ok, value}} when is_binary(value) or is_nil(value) -> - {:ok, igniter, value} + def table(igniter, resource) do + igniter + |> Spark.Igniter.get_option(resource, [:postgres, :table]) + |> case do + {igniter, {:ok, value}} when is_binary(value) or is_nil(value) -> + {:ok, igniter, value} - {igniter, _} -> - {:error, igniter} + {igniter, _} -> + {:error, igniter} + end end - end - def repo(igniter, resource) do - igniter - |> Spark.Igniter.get_option(resource, [:postgres, :repo]) - |> case do - {igniter, {:ok, value}} when is_atom(value) -> - {:ok, igniter, value} + def repo(igniter, resource) do + igniter + |> Spark.Igniter.get_option(resource, [:postgres, :repo]) + |> case do + {igniter, {:ok, value}} when is_atom(value) -> + {:ok, igniter, value} - {igniter, _} -> - {:error, igniter} + {igniter, _} -> + {:error, igniter} + end end - end - def add_postgres_extension(igniter, repo_name, extension) do - Igniter.Project.Module.find_and_update_module!(igniter, repo_name, fn zipper -> - case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do - {:ok, zipper} -> - case Igniter.Code.List.append_new_to_list(zipper, extension) do - {:ok, zipper} -> - {:ok, zipper} - - _ -> - {:warning, - "Could not add installed extension #{inspect(extension)} to #{inspect(repo_name)}.installed_extensions/0"} - end - - _ -> - zipper = Sourceror.Zipper.rightmost(zipper) + def add_postgres_extension(igniter, repo_name, extension) do + Igniter.Project.Module.find_and_update_module!(igniter, repo_name, fn zipper -> + case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do + {:ok, zipper} -> + case Igniter.Code.List.append_new_to_list(zipper, extension) do + {:ok, zipper} -> + {:ok, zipper} + + _ -> + {:warning, + "Could not add installed extension #{inspect(extension)} to #{inspect(repo_name)}.installed_extensions/0"} + end + + _ -> + zipper = Sourceror.Zipper.rightmost(zipper) + + code = """ + def installed_extensions do + [#{inspect(extension)}] + end + """ + + {:ok, Igniter.Code.Common.add_code(zipper, code)} + end + end) + end - code = """ - def installed_extensions do - [#{inspect(extension)}] + def select_repo(igniter, opts \\ []) do + label = Keyword.get(opts, :label, "Which repo should be used?") + generate = Keyword.get(opts, :generate?, false) + + case list_repos(igniter) do + {igniter, []} -> + if generate do + repo = Igniter.Project.Module.module_name(igniter, "Repo") + otp_app = Igniter.Project.Application.app_name(igniter) + + igniter = + Igniter.Project.Module.create_module( + igniter, + repo, + default_repo_contents(otp_app, repo, opts), + opts + ) + + {igniter, repo} + else + {igniter, nil} end - """ - - {:ok, Igniter.Code.Common.add_code(zipper, code)} - end - end) - end - - def select_repo(igniter, opts \\ []) do - label = Keyword.get(opts, :label, "Which repo should be used?") - generate = Keyword.get(opts, :generate?, false) - - case list_repos(igniter) do - {igniter, []} -> - if generate do - repo = Igniter.Project.Module.module_name(igniter, "Repo") - otp_app = Igniter.Project.Application.app_name(igniter) - - igniter = - Igniter.Project.Module.create_module( - igniter, - repo, - default_repo_contents(otp_app, repo, opts), - opts - ) + {igniter, [repo]} -> {igniter, repo} - else - {igniter, nil} - end - {igniter, [repo]} -> - {igniter, repo} + {igniter, repos} -> + {igniter, Owl.IO.select(repos, label: label, render_as: &inspect/1)} + end + end - {igniter, repos} -> - {igniter, Owl.IO.select(repos, label: label, render_as: &inspect/1)} + def list_repos(igniter) do + Igniter.Project.Module.find_all_matching_modules(igniter, fn _mod, zipper -> + move_to_repo_use(zipper) != :error + end) end - end - def list_repos(igniter) do - Igniter.Project.Module.find_all_matching_modules(igniter, fn _mod, zipper -> - move_to_repo_use(zipper) != :error - end) - end + defp move_to_repo_use(zipper) do + Igniter.Code.Function.move_to_function_call(zipper, :use, [1, 2], fn zipper -> + Igniter.Code.Function.argument_equals?( + zipper, + 0, + AshPostgres.Repo + ) + end) + end - defp move_to_repo_use(zipper) do - Igniter.Code.Function.move_to_function_call(zipper, :use, [1, 2], fn zipper -> - Igniter.Code.Function.argument_equals?( - zipper, - 0, - AshPostgres.Repo - ) - end) - end + @doc false + def get_min_pg_version(name, opts) do + if opts[:yes] do + %Version{major: 13, minor: 0, patch: 0} + else + lead_in = """ + Generating #{inspect(name)} - @doc false - def get_min_pg_version(name, opts) do - if opts[:yes] do - %Version{major: 13, minor: 0, patch: 0} - else - lead_in = """ - Generating #{inspect(name)} + What is the minimum PostgreSQL version you will be using? - What is the minimum PostgreSQL version you will be using? + AshPostgres uses this information when generating queries and migrations, + to choose the best available features for your version of PostgreSQL. + """ - AshPostgres uses this information when generating queries and migrations, - to choose the best available features for your version of PostgreSQL. - """ + format_request = + """ + Please enter the version in the format major.minor.patch (e.g. 13.4.0) - format_request = - """ - Please enter the version in the format major.minor.patch (e.g. 13.4.0) + Default: 16.0.0 - Default: 16.0.0 + ❯ + """ - ❯ - """ + prompt = + if opts[:invalid_loop?] do + format_request + else + "#{lead_in}\n\n#{format_request}" + end - prompt = - if opts[:invalid_loop?] do - format_request - else - "#{lead_in}\n\n#{format_request}" + prompt + |> String.trim_trailing() + |> Mix.shell().prompt() + |> String.trim() + |> case do + "" -> "16.0.0" + input -> input + end + |> Version.parse() + |> case do + {:ok, version} -> version + :error -> get_min_pg_version(name, Keyword.put(opts, :invalid_loop?, true)) end - - prompt - |> String.trim_trailing() - |> Mix.shell().prompt() - |> String.trim() - |> case do - "" -> "16.0.0" - input -> input - end - |> Version.parse() - |> case do - {:ok, version} -> version - :error -> get_min_pg_version(name, Keyword.put(opts, :invalid_loop?, true)) end end end diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 6816117a..bd6c4a36 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -1,156 +1,186 @@ -defmodule Mix.Tasks.AshPostgres.Gen.Resources do - use Igniter.Mix.Task - - @example "mix ash_postgres.gen.resource MyApp.MyDomain" - - @shortdoc "Generates or updates resources based on a database schema" - - @moduledoc """ - #{@shortdoc} - - ## Example - - `#{@example}` - - ## Domain - - The domain will be generated if it does not exist. If you aren't sure, - we suggest using something like `MyApp.App`. - - ## Options - - - `repo`, `r` - The repo or repos to generate resources for, comma separated. Can be specified multiple times. Defaults to all repos. - - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. - - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. - - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. - - `extend`, `e` - Extension or extensions to apply to the generated resources. See `mix ash.patch.extend` for more. - - `yes`, `y` - Answer yes (or skip) to all questions. - - ## Tables - - When specifying tables to include with `--tables`, you can specify the table name, or the schema and table name separated by a period. - For example, `users` will generate resources for the `users` table in the `public` schema, but `accounts.users` will generate resources for the `users` table in the `accounts` schema. - - To include all tables in a given schema, add a period only with no table name, i.e `schema.`, i.e `accounts.`. - - When skipping tables with `--skip-tables`, the same rules apply, except that the `schema.` format is not supported. - """ - - @impl Igniter.Mix.Task - def info(_argv, _parent) do - %Igniter.Mix.Task.Info{ - positional: [:domain], - example: @example, - schema: [ - repo: :keep, - yes: :boolean, - tables: :keep, - skip_tables: :keep, - extend: :keep, - snapshots_only: :boolean, - domain: :keep - ], - aliases: [ - t: :tables, - y: :boolean, - r: :repo, - e: :extend, - d: :domain, - s: :skip_tables - ] - } - end +if Code.ensure_loaded?(Igniter) do + defmodule Mix.Tasks.AshPostgres.Gen.Resources do + use Igniter.Mix.Task + + @example "mix ash_postgres.gen.resource MyApp.MyDomain" + + @shortdoc "Generates resources based on a database schema" + + @moduledoc """ + #{@shortdoc} + + ## Example + + `#{@example}` + + ## Domain + + The domain will be generated if it does not exist. If you aren't sure, + we suggest using something like `MyApp.App`. + + ## Options + + - `repo`, `r` - The repo or repos to generate resources for, comma separated. Can be specified multiple times. Defaults to all repos. + - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. + - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. + - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. + - `extend`, `e` - Extension or extensions to apply to the generated resources. See `mix ash.patch.extend` for more. + - `yes`, `y` - Answer yes (or skip) to all questions. + + ## Tables + + When specifying tables to include with `--tables`, you can specify the table name, or the schema and table name separated by a period. + For example, `users` will generate resources for the `users` table in the `public` schema, but `accounts.users` will generate resources for the `users` table in the `accounts` schema. + + To include all tables in a given schema, add a period only with no table name, i.e `schema.`, i.e `accounts.`. + + When skipping tables with `--skip-tables`, the same rules apply, except that the `schema.` format is not supported. + """ + + @impl Igniter.Mix.Task + def info(_argv, _parent) do + %Igniter.Mix.Task.Info{ + positional: [:domain], + example: @example, + schema: [ + repo: :keep, + yes: :boolean, + tables: :keep, + skip_tables: :keep, + extend: :keep, + snapshots_only: :boolean, + domain: :keep + ], + aliases: [ + t: :tables, + y: :boolean, + r: :repo, + e: :extend, + d: :domain, + s: :skip_tables + ] + } + end - @impl Igniter.Mix.Task - def igniter(igniter, argv) do - Mix.Task.run("compile") - - {%{domain: domain}, argv} = positional_args!(argv) - - domain = Igniter.Project.Module.parse(domain) - - options = options!(argv) - - repos = - options[:repo] || - Mix.Project.config()[:app] - |> Application.get_env(:ecto_repos, []) - - repos = - repos - |> List.wrap() - |> Enum.map(fn v -> - if is_binary(v) do - Igniter.Project.Module.parse(v) - else - v - end - end) - - case repos do - [] -> - igniter - |> Igniter.add_warning("No ecto repos configured.") - - repos -> - Mix.shell().info("Generating resources from #{inspect(repos)}") - - prompt = - """ - - Would you like to generate migrations for the current structure? (recommended) - - If #{IO.ANSI.green()}yes#{IO.ANSI.reset()}: - We will generate migrations based on the generated resources. - You should then change your database name in your config, and - run `mix ash.setup`. - - If you already have ecto migrations you'd like to use, run - this command with `--snapshots-only`, in which case only resource - snapshots will be generated. - #{IO.ANSI.green()} - Going forward, your resources will be the source of truth.#{IO.ANSI.reset()} - #{IO.ANSI.red()} - *WARNING* - - If you run `mix ash.reset` after this command without updating - your config, you will be *deleting the database you just used to - generate these resources*!#{IO.ANSI.reset()} - - If #{IO.ANSI.red()}no#{IO.ANSI.reset()}: - - We will not generate any migrations. This means you have migrations already that - can get you from zero to the current starting point. - #{IO.ANSI.yellow()} - You will have to hand-write migrations from this point on.#{IO.ANSI.reset()} - """ - - options = - if options[:yes] || Mix.shell().yes?(prompt) do - Keyword.put(options, :no_migrations, false) - else - Keyword.put(options, :no_migrations, true) - end + @impl Igniter.Mix.Task + def igniter(igniter, argv) do + Mix.Task.run("compile") - migration_opts = - if options[:snapshots_only] do - ["--snapshots-only"] - else - [] - end + {%{domain: domain}, argv} = positional_args!(argv) - igniter - |> Igniter.compose_task("ash.gen.domain", [inspect(domain), "--ignore-if-exists"]) - |> AshPostgres.ResourceGenerator.generate(repos, domain, options) - |> then(fn igniter -> - if options[:no_migrations] do - igniter + domain = Igniter.Project.Module.parse(domain) + + options = options!(argv) + + repos = + options[:repo] || + Mix.Project.config()[:app] + |> Application.get_env(:ecto_repos, []) + + repos = + repos + |> List.wrap() + |> Enum.map(fn v -> + if is_binary(v) do + Igniter.Project.Module.parse(v) else - Igniter.add_task(igniter, "ash_postgres.generate_migrations", [ - "import_resources" | migration_opts - ]) + v end end) + + case repos do + [] -> + igniter + |> Igniter.add_warning("No ecto repos configured.") + + repos -> + Mix.shell().info("Generating resources from #{inspect(repos)}") + + prompt = + """ + + Would you like to generate migrations for the current structure? (recommended) + + If #{IO.ANSI.green()}yes#{IO.ANSI.reset()}: + We will generate migrations based on the generated resources. + You should then change your database name in your config, and + run `mix ash.setup`. + + If you already have ecto migrations you'd like to use, run + this command with `--snapshots-only`, in which case only resource + snapshots will be generated. + #{IO.ANSI.green()} + Going forward, your resources will be the source of truth.#{IO.ANSI.reset()} + #{IO.ANSI.red()} + *WARNING* + + If you run `mix ash.reset` after this command without updating + your config, you will be *deleting the database you just used to + generate these resources*!#{IO.ANSI.reset()} + + If #{IO.ANSI.red()}no#{IO.ANSI.reset()}: + + We will not generate any migrations. This means you have migrations already that + can get you from zero to the current starting point. + #{IO.ANSI.yellow()} + You will have to hand-write migrations from this point on.#{IO.ANSI.reset()} + """ + + options = + if options[:yes] || Mix.shell().yes?(prompt) do + Keyword.put(options, :no_migrations, false) + else + Keyword.put(options, :no_migrations, true) + end + + migration_opts = + if options[:snapshots_only] do + ["--snapshots-only"] + else + [] + end + + igniter + |> Igniter.compose_task("ash.gen.domain", [inspect(domain), "--ignore-if-exists"]) + |> AshPostgres.ResourceGenerator.generate(repos, domain, options) + |> then(fn igniter -> + if options[:no_migrations] do + igniter + else + Igniter.add_task(igniter, "ash_postgres.generate_migrations", [ + "import_resources" | migration_opts + ]) + end + end) + end + end + end +else + defmodule Mix.Tasks.AshPostgres.Gen.Resources do + @example "mix ash_postgres.gen.resource MyApp.MyDomain" + + @shortdoc "Generates resources based on a database schema" + + @moduledoc """ + #{@shortdoc} + + ## Example + + `#{@example}` + """ + + use Mix.Task + + def run(_argv) do + Mix.shell().error(""" + The task 'ash_postgres.gen.resources' requires igniter to be run. + + Please install igniter and try again. + + For more information, see: https://hexdocs.pm/igniter + """) + + exit({:shutdown, 1}) end end end diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 341aa1a0..d59e31dd 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -1,286 +1,287 @@ -defmodule Mix.Tasks.AshPostgres.Install do - @moduledoc "Installs AshPostgres. Should be run with `mix igniter.install ash_postgres`" - @shortdoc @moduledoc - require Igniter.Code.Common - require Igniter.Code.Function - use Igniter.Mix.Task - - @impl true - def info(_argv, _source) do - %Igniter.Mix.Task.Info{ - schema: [ - yes: :boolean, - repo: :string - ], - aliases: [ - y: :yes, - r: :repo - ] - } - end +if Code.ensure_loaded?(Igniter) do + defmodule Mix.Tasks.AshPostgres.Install do + @moduledoc "Installs AshPostgres. Should be run with `mix igniter.install ash_postgres`" + @shortdoc @moduledoc + require Igniter.Code.Common + require Igniter.Code.Function + use Igniter.Mix.Task + + @impl true + def info(_argv, _source) do + %Igniter.Mix.Task.Info{ + schema: [ + yes: :boolean, + repo: :string + ], + aliases: [ + y: :yes, + r: :repo + ] + } + end - @impl true - def igniter(igniter, argv) do - opts = options!(argv) + @impl true + def igniter(igniter, argv) do + opts = options!(argv) - repo = - case opts[:repo] do - nil -> - Igniter.Project.Module.module_name(igniter, "Repo") + repo = + case opts[:repo] do + nil -> + Igniter.Project.Module.module_name(igniter, "Repo") - repo -> - Igniter.Project.Module.parse(repo) - end - - otp_app = Igniter.Project.Application.app_name(igniter) - - igniter - |> Igniter.Project.Formatter.import_dep(:ash_postgres) - |> setup_aliases() - |> setup_repo_module(otp_app, repo, opts) - |> configure_config(otp_app, repo) - |> configure_dev(otp_app, repo) - |> configure_runtime(otp_app, repo) - |> configure_test(otp_app, repo) - |> setup_data_case() - |> Igniter.Project.Application.add_new_child(repo, - after: fn mod -> - case Module.split(mod) do - [_, "Telemetry"] -> true - _ -> false + repo -> + Igniter.Project.Module.parse(repo) end - end - ) - |> Spark.Igniter.prepend_to_section_order(:"Ash.Resource", [:postgres]) - |> Ash.Igniter.codegen("initialize") - end - defp setup_aliases(igniter) do - is_ecto_setup = &Igniter.Code.Common.nodes_equal?(&1, "ecto.setup") + otp_app = Igniter.Project.Application.app_name(igniter) - is_ecto_create_or_migrate = - fn zipper -> - Igniter.Code.Common.nodes_equal?(zipper, "ecto.create --quiet") or - Igniter.Code.Common.nodes_equal?(zipper, "ecto.create") or - Igniter.Code.Common.nodes_equal?(zipper, "ecto.migrate --quiet") or - Igniter.Code.Common.nodes_equal?(zipper, "ecto.migrate") - end - - igniter - |> Igniter.Project.TaskAliases.modify_existing_alias( - "test", - &Igniter.Code.List.remove_from_list(&1, is_ecto_create_or_migrate) - ) - |> Igniter.Project.TaskAliases.modify_existing_alias( - "test", - &Igniter.Code.List.replace_in_list( - &1, - is_ecto_setup, - Sourceror.parse_string!("\"ash.setup\"") + igniter + |> Igniter.Project.Formatter.import_dep(:ash_postgres) + |> setup_aliases() + |> setup_repo_module(otp_app, repo, opts) + |> configure_config(otp_app, repo) + |> configure_dev(otp_app, repo) + |> configure_runtime(otp_app, repo) + |> configure_test(otp_app, repo) + |> setup_data_case() + |> Igniter.Project.Application.add_new_child(repo, + after: fn mod -> + case Module.split(mod) do + [_, "Telemetry"] -> true + _ -> false + end + end ) - ) - |> Igniter.Project.TaskAliases.add_alias("test", ["ash.setup --quiet", "test"], - if_exists: {:prepend, "ash.setup --quiet"} - ) - |> run_seeds_on_setup() - end + |> Spark.Igniter.prepend_to_section_order(:"Ash.Resource", [:postgres]) + |> Ash.Igniter.codegen("initialize") + end + + defp setup_aliases(igniter) do + is_ecto_setup = &Igniter.Code.Common.nodes_equal?(&1, "ecto.setup") + + is_ecto_create_or_migrate = + fn zipper -> + Igniter.Code.Common.nodes_equal?(zipper, "ecto.create --quiet") or + Igniter.Code.Common.nodes_equal?(zipper, "ecto.create") or + Igniter.Code.Common.nodes_equal?(zipper, "ecto.migrate --quiet") or + Igniter.Code.Common.nodes_equal?(zipper, "ecto.migrate") + end - defp run_seeds_on_setup(igniter) do - if Igniter.exists?(igniter, "priv/repo/seeds.exs") do igniter - |> Igniter.Project.TaskAliases.add_alias("setup", "ash.setup", - if_exists: {:replace_or_append, "ecto.setup", "ash.setup"} + |> Igniter.Project.TaskAliases.modify_existing_alias( + "test", + &Igniter.Code.List.remove_from_list(&1, is_ecto_create_or_migrate) ) - |> Igniter.Project.TaskAliases.add_alias("setup", "run priv/repo/seeds.exs", - if_exists: :append + |> Igniter.Project.TaskAliases.modify_existing_alias( + "test", + &Igniter.Code.List.replace_in_list( + &1, + is_ecto_setup, + Sourceror.parse_string!("\"ash.setup\"") + ) + ) + |> Igniter.Project.TaskAliases.add_alias("test", ["ash.setup --quiet", "test"], + if_exists: {:prepend, "ash.setup --quiet"} ) - else - Igniter.Project.TaskAliases.add_alias(igniter, "setup", "ash.setup") + |> run_seeds_on_setup() end - end - defp configure_config(igniter, otp_app, repo) do - Igniter.Project.Config.configure( - igniter, - "config.exs", - otp_app, - [:ecto_repos], - [repo], - updater: fn zipper -> - Igniter.Code.List.prepend_new_to_list( - zipper, - repo + defp run_seeds_on_setup(igniter) do + if Igniter.exists?(igniter, "priv/repo/seeds.exs") do + igniter + |> Igniter.Project.TaskAliases.add_alias("setup", "ash.setup", + if_exists: {:replace_or_append, "ecto.setup", "ash.setup"} ) + |> Igniter.Project.TaskAliases.add_alias("setup", "run priv/repo/seeds.exs", + if_exists: :append + ) + else + Igniter.Project.TaskAliases.add_alias(igniter, "setup", "ash.setup") end - ) - end - - defp configure_runtime(igniter, otp_app, repo) do - default_runtime = """ - import Config - - if config_env() == :prod do - database_url = - System.get_env("DATABASE_URL") || - raise \"\"\" - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - \"\"\" - - config #{inspect(otp_app)}, #{inspect(repo)}, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") end - """ - igniter - |> Igniter.create_or_update_elixir_file("config/runtime.exs", default_runtime, fn zipper -> - if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo, :url]) do - zipper - else - patterns = [ - """ - if config_env() == :prod do - __cursor__() - end - """, - """ - if :prod == config_env() do - __cursor__() - end - """ - ] - - zipper - |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) - |> case do - {:ok, zipper} -> - case Igniter.Code.Function.move_to_function_call_in_current_scope( - zipper, - :=, - 2, - fn call -> - Igniter.Code.Function.argument_matches_pattern?( - call, - 0, - {:database_url, _, ctx} when is_atom(ctx) - ) - end - ) do - {:ok, _zipper} -> - zipper - |> Igniter.Project.Config.modify_configuration_code( - [repo, :url], - otp_app, - {:database_url, [], nil} - ) - |> Igniter.Project.Config.modify_configuration_code( - [repo, :pool_size], - otp_app, - Sourceror.parse_string!(""" - String.to_integer(System.get_env("POOL_SIZE") || "10") - """) - ) - |> then(&{:ok, &1}) + defp configure_config(igniter, otp_app, repo) do + Igniter.Project.Config.configure( + igniter, + "config.exs", + otp_app, + [:ecto_repos], + [repo], + updater: fn zipper -> + Igniter.Code.List.prepend_new_to_list( + zipper, + repo + ) + end + ) + end - _ -> - Igniter.Code.Common.add_code(zipper, """ - database_url = - System.get_env("DATABASE_URL") || - raise \"\"\" - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - \"\"\" - - config #{inspect(otp_app)}, Helpdesk.Repo, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") - """) - end + defp configure_runtime(igniter, otp_app, repo) do + default_runtime = """ + import Config + + if config_env() == :prod do + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, #{inspect(repo)}, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + end + """ - :error -> - Igniter.Code.Common.add_code(zipper, """ + igniter + |> Igniter.create_or_update_elixir_file("config/runtime.exs", default_runtime, fn zipper -> + if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo, :url]) do + zipper + else + patterns = [ + """ if config_env() == :prod do - database_url = - System.get_env("DATABASE_URL") || - raise \"\"\" - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - \"\"\" - - config #{inspect(otp_app)}, Helpdesk.Repo, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + __cursor__() end - """) + """, + """ + if :prod == config_env() do + __cursor__() + end + """ + ] + + zipper + |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) + |> case do + {:ok, zipper} -> + case Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :=, + 2, + fn call -> + Igniter.Code.Function.argument_matches_pattern?( + call, + 0, + {:database_url, _, ctx} when is_atom(ctx) + ) + end + ) do + {:ok, _zipper} -> + zipper + |> Igniter.Project.Config.modify_configuration_code( + [repo, :url], + otp_app, + {:database_url, [], nil} + ) + |> Igniter.Project.Config.modify_configuration_code( + [repo, :pool_size], + otp_app, + Sourceror.parse_string!(""" + String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + ) + |> then(&{:ok, &1}) + + _ -> + Igniter.Code.Common.add_code(zipper, """ + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, Helpdesk.Repo, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + end + + :error -> + Igniter.Code.Common.add_code(zipper, """ + if config_env() == :prod do + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, Helpdesk.Repo, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + end + """) + end end - end - end) - end + end) + end - defp configure_dev(igniter, otp_app, repo) do - igniter - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :username], "postgres") - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :password], "postgres") - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :hostname], "localhost") - |> Igniter.Project.Config.configure_new( - "dev.exs", - otp_app, - [repo, :database], - "#{otp_app}_dev" - ) - |> Igniter.Project.Config.configure_new( - "dev.exs", - otp_app, - [repo, :show_sensitive_data_on_connection_error], - true - ) - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :pool_size], 10) - end + defp configure_dev(igniter, otp_app, repo) do + igniter + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :username], "postgres") + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :password], "postgres") + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :hostname], "localhost") + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :database], + "#{otp_app}_dev" + ) + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :show_sensitive_data_on_connection_error], + true + ) + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :pool_size], 10) + end - defp configure_test(igniter, otp_app, repo) do - database = - {:<<>>, [], - [ - "#{otp_app}_test", - {:"::", [], - [ - {{:., [], [Kernel, :to_string]}, [from_interpolation: true], - [ - {{:., [], [{:__aliases__, [alias: false], [:System]}, :get_env]}, [], - ["MIX_TEST_PARTITION"]} - ]}, - {:binary, [], Elixir} - ]} - ]} - |> Sourceror.to_string() - |> Sourceror.parse_string!() - - igniter - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :username], "postgres") - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :password], "postgres") - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :hostname], "localhost") - |> Igniter.Project.Config.configure_new( - "test.exs", - otp_app, - [repo, :database], - {:code, database} - ) - |> Igniter.Project.Config.configure_new( - "test.exs", - otp_app, - [repo, :pool], - Ecto.Adapters.SQL.Sandbox - ) - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) - |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) - |> Igniter.Project.Config.configure_new("test.exs", :logger, [:level], :warning) - end + defp configure_test(igniter, otp_app, repo) do + database = + {:<<>>, [], + [ + "#{otp_app}_test", + {:"::", [], + [ + {{:., [], [Kernel, :to_string]}, [from_interpolation: true], + [ + {{:., [], [{:__aliases__, [alias: false], [:System]}, :get_env]}, [], + ["MIX_TEST_PARTITION"]} + ]}, + {:binary, [], Elixir} + ]} + ]} + |> Sourceror.to_string() + |> Sourceror.parse_string!() + + igniter + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :username], "postgres") + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :password], "postgres") + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :hostname], "localhost") + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :database], + {:code, database} + ) + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :pool], + Ecto.Adapters.SQL.Sandbox + ) + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) + |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) + |> Igniter.Project.Config.configure_new("test.exs", :logger, [:level], :warning) + end - defp setup_data_case(igniter) do - module_name = Igniter.Project.Module.module_name(igniter, "DataCase") + defp setup_data_case(igniter) do + module_name = Igniter.Project.Module.module_name(igniter, "DataCase") - default_data_case_contents = ~s| + default_data_case_contents = ~s| @moduledoc """ This module defines the setup for tests requiring access to the application's data layer. @@ -316,171 +317,192 @@ defmodule Mix.Tasks.AshPostgres.Install do end | - igniter - |> Igniter.Project.Module.find_and_update_or_create_module( - module_name, - default_data_case_contents, - # do nothing if already exists - fn zipper -> {:ok, zipper} end, - path: Igniter.Project.Module.proper_location(igniter, module_name, :test_support) - ) - end - - defp setup_repo_module(igniter, otp_app, repo, opts) do - {exists?, igniter} = Igniter.Project.Module.module_exists(igniter, repo) - - if exists? do - Igniter.Project.Module.find_and_update_module!( - igniter, - repo, - fn zipper -> - case Igniter.Code.Module.move_to_use(zipper, Ecto.Repo) do - {:ok, _} -> - {:ok, - zipper - |> set_otp_app(otp_app) - |> Sourceror.Zipper.top() - |> use_ash_postgres_instead_of_ecto() - |> Sourceror.Zipper.top() - |> remove_adapter_option()} - - _ -> - case Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo) do - {:ok, _} -> - {:ok, zipper} + igniter + |> Igniter.Project.Module.find_and_update_or_create_module( + module_name, + default_data_case_contents, + # do nothing if already exists + fn zipper -> {:ok, zipper} end, + path: Igniter.Project.Module.proper_location(igniter, module_name, :test_support) + ) + end - _ -> - {:error, - """ - Repo module #{inspect(repo)} existed, but was not an `Ecto.Repo` or an `AshPostgres.Repo`. + defp setup_repo_module(igniter, otp_app, repo, opts) do + {exists?, igniter} = Igniter.Project.Module.module_exists(igniter, repo) + + if exists? do + Igniter.Project.Module.find_and_update_module!( + igniter, + repo, + fn zipper -> + case Igniter.Code.Module.move_to_use(zipper, Ecto.Repo) do + {:ok, _} -> + {:ok, + zipper + |> set_otp_app(otp_app) + |> Sourceror.Zipper.top() + |> use_ash_postgres_instead_of_ecto() + |> Sourceror.Zipper.top() + |> remove_adapter_option()} - Please re-run the AshPostgres installer with the `--repo` option to specify a repo. - """} - end + _ -> + case Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo) do + {:ok, _} -> + {:ok, zipper} + + _ -> + {:error, + """ + Repo module #{inspect(repo)} existed, but was not an `Ecto.Repo` or an `AshPostgres.Repo`. + + Please re-run the AshPostgres installer with the `--repo` option to specify a repo. + """} + end + end end - end + ) + else + Igniter.Project.Module.create_module( + igniter, + repo, + AshPostgres.Igniter.default_repo_contents(otp_app, repo, opts) + ) + end + |> Igniter.Project.Module.find_and_update_module!( + repo, + &configure_installed_extensions_function/1 ) - else - Igniter.Project.Module.create_module( - igniter, + |> Igniter.Project.Module.find_and_update_module!( + repo, + &configure_prefer_transaction_function/1 + ) + |> Igniter.Project.Module.find_and_update_module!( repo, - AshPostgres.Igniter.default_repo_contents(otp_app, repo, opts) + &configure_min_pg_version_function(&1, repo, opts) ) end - |> Igniter.Project.Module.find_and_update_module!( - repo, - &configure_installed_extensions_function/1 - ) - |> Igniter.Project.Module.find_and_update_module!( - repo, - &configure_prefer_transaction_function/1 - ) - |> Igniter.Project.Module.find_and_update_module!( - repo, - &configure_min_pg_version_function(&1, repo, opts) - ) - end - defp use_ash_postgres_instead_of_ecto(zipper) do - with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Ecto.Repo), - {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Ecto.Repo), - {:ok, zipper} <- - Igniter.Code.Function.update_nth_argument(zipper, 0, fn zipper -> - {:ok, Igniter.Code.Common.replace_code(zipper, AshPostgres.Repo)} - end) do - zipper - else - _ -> + defp use_ash_postgres_instead_of_ecto(zipper) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Ecto.Repo), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Ecto.Repo), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 0, fn zipper -> + {:ok, Igniter.Code.Common.replace_code(zipper, AshPostgres.Repo)} + end) do zipper + else + _ -> + zipper + end end - end - defp remove_adapter_option(zipper) do - with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo), - {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo), - {:ok, zipper} <- - Igniter.Code.Function.update_nth_argument(zipper, 1, fn values_zipper -> - Igniter.Code.Keyword.remove_keyword_key(values_zipper, :adapter) - end) do - zipper - else - _ -> + defp remove_adapter_option(zipper) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 1, fn values_zipper -> + Igniter.Code.Keyword.remove_keyword_key(values_zipper, :adapter) + end) do zipper + else + _ -> + zipper + end end - end - defp set_otp_app(zipper, otp_app) do - with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo), - {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo), - {:ok, zipper} <- - Igniter.Code.Function.update_nth_argument(zipper, 0, fn zipper -> - {:ok, Igniter.Code.Common.replace_code(zipper, AshPostgres.Repo)} - end), - {:ok, zipper} <- - Igniter.Code.Function.update_nth_argument(zipper, 1, fn values_zipper -> - values_zipper - |> Igniter.Code.Keyword.set_keyword_key(:otp_app, otp_app, fn x -> {:ok, x} end) - end) do - zipper - else - _ -> + defp set_otp_app(zipper, otp_app) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, AshPostgres.Repo), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshPostgres.Repo), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 0, fn zipper -> + {:ok, Igniter.Code.Common.replace_code(zipper, AshPostgres.Repo)} + end), + {:ok, zipper} <- + Igniter.Code.Function.update_nth_argument(zipper, 1, fn values_zipper -> + values_zipper + |> Igniter.Code.Keyword.set_keyword_key(:otp_app, otp_app, fn x -> {:ok, x} end) + end) do zipper + else + _ -> + zipper + end end - end - defp configure_installed_extensions_function(zipper) do - case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do - {:ok, zipper} -> - case Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do - {:ok, zipper} -> - Igniter.Code.List.append_new_to_list(zipper, "ash-functions") + defp configure_installed_extensions_function(zipper) do + case Igniter.Code.Function.move_to_def(zipper, :installed_extensions, 0) do + {:ok, zipper} -> + case Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do + {:ok, zipper} -> + Igniter.Code.List.append_new_to_list(zipper, "ash-functions") - :error -> - {:error, "installed_extensions/0 doesn't return a list"} - end + :error -> + {:error, "installed_extensions/0 doesn't return a list"} + end - _ -> - {:ok, - Igniter.Code.Common.add_code(zipper, """ - def installed_extensions do - # Add extensions here, and the migration generator will install them. - ["ash-functions"] - end - """)} + _ -> + {:ok, + Igniter.Code.Common.add_code(zipper, """ + def installed_extensions do + # Add extensions here, and the migration generator will install them. + ["ash-functions"] + end + """)} + end end - end - defp configure_prefer_transaction_function(zipper) do - case Igniter.Code.Function.move_to_def(zipper, :prefer_transaction?, 0) do - {:ok, zipper} -> - {:ok, zipper} - - _ -> - {:ok, - Igniter.Code.Common.add_code(zipper, """ - # Don't open unnecessary transactions - # will default to `false` in 4.0 - def prefer_transaction? do - false - end - """)} + defp configure_prefer_transaction_function(zipper) do + case Igniter.Code.Function.move_to_def(zipper, :prefer_transaction?, 0) do + {:ok, zipper} -> + {:ok, zipper} + + _ -> + {:ok, + Igniter.Code.Common.add_code(zipper, """ + # Don't open unnecessary transactions + # will default to `false` in 4.0 + def prefer_transaction? do + false + end + """)} + end + end + + defp configure_min_pg_version_function(zipper, repo, opts) do + case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do + {:ok, zipper} -> + {:ok, zipper} + + _ -> + min_pg_version = AshPostgres.Igniter.get_min_pg_version(repo, opts) + + {:ok, + Igniter.Code.Common.add_code(zipper, """ + def min_pg_version do + %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} + end + """)} + end end end +else + defmodule Mix.Tasks.AshPostgres.Install do + @moduledoc "Installs AshPostgres into a project. Should be called with `mix igniter.install ash_postgres`" + + @shortdoc @moduledoc + + use Mix.Task + + def run(_argv) do + Mix.shell().error(""" + The task 'ash_postgres.install' requires igniter to be run. - defp configure_min_pg_version_function(zipper, repo, opts) do - case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do - {:ok, zipper} -> - {:ok, zipper} + Please install igniter and try again. - _ -> - min_pg_version = AshPostgres.Igniter.get_min_pg_version(repo, opts) + For more information, see: https://hexdocs.pm/igniter + """) - {:ok, - Igniter.Code.Common.add_code(zipper, """ - def min_pg_version do - %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} - end - """)} + exit({:shutdown, 1}) end end end diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 13ced059..f67e79c3 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -1,649 +1,656 @@ -defmodule AshPostgres.ResourceGenerator do - @moduledoc false - alias AshPostgres.ResourceGenerator.Spec +if Code.ensure_loaded?(Igniter) do + defmodule AshPostgres.ResourceGenerator do + @moduledoc false + alias AshPostgres.ResourceGenerator.Spec - require Logger + require Logger - def generate(igniter, repos, domain, opts \\ []) do - {igniter, resources} = Ash.Resource.Igniter.list_resources(igniter) + def generate(igniter, repos, domain, opts \\ []) do + {igniter, resources} = Ash.Resource.Igniter.list_resources(igniter) - # This is a hack. We should be looking at compiled resources - # unlikely to ever matter given how this task will be used though. - resources = Enum.filter(resources, &Code.ensure_loaded?/1) + # This is a hack. We should be looking at compiled resources + # unlikely to ever matter given how this task will be used though. + resources = Enum.filter(resources, &Code.ensure_loaded?/1) - igniter = Igniter.include_all_elixir_files(igniter) + igniter = Igniter.include_all_elixir_files(igniter) - opts = handle_csv_opts(opts, [:tables, :skip_tables, :extend]) + opts = handle_csv_opts(opts, [:tables, :skip_tables, :extend]) - specs = - repos - |> Enum.flat_map(&Spec.tables(&1, skip_tables: opts[:skip_tables], tables: opts[:tables])) - |> Enum.map(fn %{table_name: table} = spec -> - resource = - table - |> Inflex.singularize() - |> Macro.camelize() - |> then(&Module.concat([domain, &1])) + specs = + repos + |> Enum.flat_map(&Spec.tables(&1, skip_tables: opts[:skip_tables], tables: opts[:tables])) + |> Enum.map(fn %{table_name: table} = spec -> + resource = + table + |> Inflex.singularize() + |> Macro.camelize() + |> then(&Module.concat([domain, &1])) - %{spec | resource: resource} - end) - |> Enum.group_by(& &1.resource) - |> Enum.map(fn - {_resource, [single]} -> - single - - {resource, specs} -> - raise """ - Duplicate resource names detected across multiple repos: #{inspect(resource)} - - #{inspect(Enum.map(specs, & &1.repo))} - - To address this, run this command separately for each repo and specify the - `--domain` option to put the resources into a separate domain, or omit the table - with `--tables` or `--skip-tables` - """ - end) - |> Spec.add_relationships(resources, opts) - - Enum.reduce(specs, igniter, fn table_spec, igniter -> - table_to_resource(igniter, table_spec, domain, opts) - end) - end - - defp handle_csv_opts(opts, keys) do - Enum.reduce(keys, opts, fn key, opts -> - opts - |> Keyword.get_values(key) - |> case do - [] -> - opts + %{spec | resource: resource} + end) + |> Enum.group_by(& &1.resource) + |> Enum.map(fn + {_resource, [single]} -> + single - values -> - values - |> Enum.join(",") - |> String.split(",", trim: true) - |> then(&Keyword.put(opts, key, &1)) - end - end) - end + {resource, specs} -> + raise """ + Duplicate resource names detected across multiple repos: #{inspect(resource)} - defp table_to_resource( - igniter, - %AshPostgres.ResourceGenerator.Spec{} = table_spec, - domain, - opts - ) do - no_migrate_flag = - if opts[:no_migrations] do - "migrate? false" - end + #{inspect(Enum.map(specs, & &1.repo))} - resource = - """ - use Ash.Resource, - domain: #{inspect(domain)}, - data_layer: AshPostgres.DataLayer - - postgres do - table #{inspect(table_spec.table_name)} - repo #{inspect(table_spec.repo)} - #{no_migrate_flag} - #{references(table_spec, opts[:no_migrations])} - #{custom_indexes(table_spec, opts[:no_migrations])} - #{check_constraints(table_spec, opts[:no_migrations])} - #{skip_unique_indexes(table_spec)} - #{identity_index_names(table_spec)} - end + To address this, run this command separately for each repo and specify the + `--domain` option to put the resources into a separate domain, or omit the table + with `--tables` or `--skip-tables` + """ + end) + |> Spec.add_relationships(resources, opts) - attributes do - #{attributes(table_spec)} - end - """ - |> add_identities(table_spec) - |> add_relationships(table_spec) - - igniter - |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) - |> Igniter.Project.Module.create_module(table_spec.resource, resource) - |> then(fn igniter -> - if opts[:extend] && opts[:extend] != [] do - Igniter.compose_task(igniter, "ash.patch.extend", [ - table_spec.resource | opts[:extend] || [] - ]) - else - igniter - end - end) - end + Enum.reduce(specs, igniter, fn table_spec, igniter -> + table_to_resource(igniter, table_spec, domain, opts) + end) + end - defp check_constraints(%{check_constraints: _check_constraints}, true) do - "" - end + defp handle_csv_opts(opts, keys) do + Enum.reduce(keys, opts, fn key, opts -> + opts + |> Keyword.get_values(key) + |> case do + [] -> + opts + + values -> + values + |> Enum.join(",") + |> String.split(",", trim: true) + |> then(&Keyword.put(opts, key, &1)) + end + end) + end - defp check_constraints(%{check_constraints: []}, _) do - "" - end + defp table_to_resource( + igniter, + %AshPostgres.ResourceGenerator.Spec{} = table_spec, + domain, + opts + ) do + no_migrate_flag = + if opts[:no_migrations] do + "migrate? false" + end - defp check_constraints(%{check_constraints: check_constraints}, _) do - check_constraints = - Enum.map_join(check_constraints, "\n", fn check_constraint -> + resource = """ - check_constraint :#{check_constraint.column}, "#{check_constraint.name}", check: "#{check_constraint.expression}", message: "is invalid" + use Ash.Resource, + domain: #{inspect(domain)}, + data_layer: AshPostgres.DataLayer + + postgres do + table #{inspect(table_spec.table_name)} + repo #{inspect(table_spec.repo)} + #{no_migrate_flag} + #{references(table_spec, opts[:no_migrations])} + #{custom_indexes(table_spec, opts[:no_migrations])} + #{check_constraints(table_spec, opts[:no_migrations])} + #{skip_unique_indexes(table_spec)} + #{identity_index_names(table_spec)} + end + + attributes do + #{attributes(table_spec)} + end """ + |> add_identities(table_spec) + |> add_relationships(table_spec) + + igniter + |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) + |> Igniter.Project.Module.create_module(table_spec.resource, resource) + |> then(fn igniter -> + if opts[:extend] && opts[:extend] != [] do + Igniter.compose_task(igniter, "ash.patch.extend", [ + table_spec.resource | opts[:extend] || [] + ]) + else + igniter + end end) - - """ - check_constraints do - #{check_constraints} end - """ - end - defp skip_unique_indexes(%{indexes: indexes}) do - indexes - |> Enum.filter(fn %{unique?: unique?, columns: columns} -> - unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) - end) - |> Enum.reject(&index_as_identity?/1) - |> case do - [] -> - "" - - indexes -> - """ - skip_unique_indexes [#{Enum.map_join(indexes, ",", &":#{&1.identity_name}")}] - """ + defp check_constraints(%{check_constraints: _check_constraints}, true) do + "" end - end - defp identity_index_names(%{indexes: indexes}) do - indexes - |> Enum.filter(fn %{unique?: unique?, columns: columns} -> - unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) - end) - |> case do - [] -> - [] - - indexes -> - indexes - |> Enum.map_join(", ", fn index -> - "#{index.identity_name}: \"#{index.name}\"" - end) - |> then(&"identity_index_names [#{&1}]") + defp check_constraints(%{check_constraints: []}, _) do + "" end - end - defp add_identities(str, %{indexes: indexes}) do - indexes - |> Enum.filter(fn %{unique?: unique?, columns: columns} -> - unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) - end) - |> Enum.map(fn index -> - name = index.identity_name + defp check_constraints(%{check_constraints: check_constraints}, _) do + check_constraints = + Enum.map_join(check_constraints, "\n", fn check_constraint -> + """ + check_constraint :#{check_constraint.column}, "#{check_constraint.name}", check: "#{check_constraint.expression}", message: "is invalid" + """ + end) - fields = "[" <> Enum.map_join(index.columns, ", ", &":#{&1}") <> "]" + """ + check_constraints do + #{check_constraints} + end + """ + end - case identity_options(index) do - "" -> - "identity :#{name}, #{fields}" + defp skip_unique_indexes(%{indexes: indexes}) do + indexes + |> Enum.filter(fn %{unique?: unique?, columns: columns} -> + unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end) + |> Enum.reject(&index_as_identity?/1) + |> case do + [] -> + "" - options -> + indexes -> """ - identity :#{name}, #{fields} do - #{options} - end + skip_unique_indexes [#{Enum.map_join(indexes, ",", &":#{&1.identity_name}")}] """ end - end) - |> case do - [] -> - str - - identities -> - """ - #{str} - - identities do - #{Enum.join(identities, "\n")} - end - """ end - end - - defp identity_options(index) do - "" - |> add_identity_where(index) - |> add_nils_distinct?(index) - end - defp add_identity_where(str, %{where_clause: nil}), do: str + defp identity_index_names(%{indexes: indexes}) do + indexes + |> Enum.filter(fn %{unique?: unique?, columns: columns} -> + unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end) + |> case do + [] -> + [] - defp add_identity_where(str, %{name: name, where_clause: where_clause}) do - Logger.warning(""" - Index #{name} has been left commented out in its resource - Manual conversion of `#{where_clause}` to an Ash expression is required. - """) + indexes -> + indexes + |> Enum.map_join(", ", fn index -> + "#{index.identity_name}: \"#{index.name}\"" + end) + |> then(&"identity_index_names [#{&1}]") + end + end - """ - #{str} - # Express `#{where_clause}` as an Ash expression - # where expr(...) - """ - end + defp add_identities(str, %{indexes: indexes}) do + indexes + |> Enum.filter(fn %{unique?: unique?, columns: columns} -> + unique? && Enum.all?(columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end) + |> Enum.map(fn index -> + name = index.identity_name - defp add_nils_distinct?(str, %{nils_distinct?: false}) do - "#{str}\n nils_distinct? false" - end + fields = "[" <> Enum.map_join(index.columns, ", ", &":#{&1}") <> "]" - defp add_nils_distinct?(str, _), do: str + case identity_options(index) do + "" -> + "identity :#{name}, #{fields}" - defp add_relationships(str, %{relationships: []}) do - str - end - - defp add_relationships(str, %{relationships: relationships} = spec) do - relationships - |> Enum.map_join("\n", fn relationship -> - case relationship_options(spec, relationship) do - "" -> - "#{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)}" + options -> + """ + identity :#{name}, #{fields} do + #{options} + end + """ + end + end) + |> case do + [] -> + str - options -> + identities -> """ - #{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)} do - #{options} + #{str} + + identities do + #{Enum.join(identities, "\n")} end """ end - end) - |> then(fn rels -> - """ - #{str} - - relationships do - #{rels} - end - """ - end) - end - - defp relationship_options(spec, %{type: :belongs_to} = rel) do - case Enum.find(spec.attributes, fn attribute -> - attribute.name == rel.source_attribute - end) do - %{ - default: default, - generated?: generated?, - source: source, - name: name - } - when not is_nil(default) or generated? or source != name -> - "define_attribute? false" - |> add_destination_attribute(rel, "id") - |> add_source_attribute(rel, "#{rel.name}_id") - |> add_allow_nil(rel) - |> add_filter(rel) - - attribute -> - "" - |> add_destination_attribute(rel, "id") - |> add_source_attribute(rel, "#{rel.name}_id") - |> add_allow_nil(rel) - |> add_primary_key(attribute.primary_key?) - |> add_attribute_type(attribute) - |> add_filter(rel) end - end - - defp relationship_options(_spec, rel) do - default_destination_attribute = - rel.source - |> Module.split() - |> List.last() - |> Macro.underscore() - |> Kernel.<>("_id") - - "" - |> add_destination_attribute(rel, default_destination_attribute) - |> add_source_attribute(rel, "id") - |> add_filter(rel) - end - - defp add_filter(str, %{match_with: []}), do: str - defp add_filter(str, %{match_with: match_with}) do - filter = - Enum.map_join(match_with, " and ", fn {source, dest} -> - "parent(#{source}) == #{dest}" - end) - - "#{str}\n filter expr(#{filter})" - end + defp identity_options(index) do + "" + |> add_identity_where(index) + |> add_nils_distinct?(index) + end - defp add_attribute_type(str, %{attr_type: :uuid}), do: str + defp add_identity_where(str, %{where_clause: nil}), do: str - defp add_attribute_type(str, %{attr_type: attr_type}) do - "#{str}\n attribute_type :#{attr_type}" - end + defp add_identity_where(str, %{name: name, where_clause: where_clause}) do + Logger.warning(""" + Index #{name} has been left commented out in its resource + Manual conversion of `#{where_clause}` to an Ash expression is required. + """) - defp add_destination_attribute(str, rel, default) do - if rel.destination_attribute == default do - str - else - "#{str}\n destination_attribute :#{rel.destination_attribute}" + """ + #{str} + # Express `#{where_clause}` as an Ash expression + # where expr(...) + """ end - end - defp add_source_attribute(str, rel, default) do - if rel.source_attribute == default do - str - else - "#{str}\n source_attribute :#{rel.source_attribute}" + defp add_nils_distinct?(str, %{nils_distinct?: false}) do + "#{str}\n nils_distinct? false" end - end - defp references(_table_spec, true) do - "" - end - - defp references(table_spec, _) do - table_spec.foreign_keys - |> Enum.flat_map(fn %Spec.ForeignKey{} = foreign_key -> - default_name = "#{table_spec.table_name}_#{foreign_key.column}_fkey" + defp add_nils_distinct?(str, _), do: str - if default_name == foreign_key.constraint_name and - foreign_key.on_update == "NO ACTION" and - foreign_key.on_delete == "NO ACTION" and - foreign_key.match_type in ["SIMPLE", "NONE"] do - [] - else - relationship = - Enum.find(table_spec.relationships, fn relationship -> - relationship.type == :belongs_to and - relationship.constraint_name == foreign_key.constraint_name - end) - - if relationship do - relationship = relationship.name + defp add_relationships(str, %{relationships: []}) do + str + end - options = - "" - |> add_on(:update, foreign_key.on_update) - |> add_on(:delete, foreign_key.on_delete) - |> add_match_with(foreign_key.match_with) - |> add_match_type(foreign_key.match_type) + defp add_relationships(str, %{relationships: relationships} = spec) do + relationships + |> Enum.map_join("\n", fn relationship -> + case relationship_options(spec, relationship) do + "" -> + "#{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)}" - [ + options -> """ - reference :#{relationship} do - #{options} + #{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)} do + #{options} end """ - ] - else - [] end + end) + |> then(fn rels -> + """ + #{str} + + relationships do + #{rels} + end + """ + end) + end + + defp relationship_options(spec, %{type: :belongs_to} = rel) do + case Enum.find(spec.attributes, fn attribute -> + attribute.name == rel.source_attribute + end) do + %{ + default: default, + generated?: generated?, + source: source, + name: name + } + when not is_nil(default) or generated? or source != name -> + "define_attribute? false" + |> add_destination_attribute(rel, "id") + |> add_source_attribute(rel, "#{rel.name}_id") + |> add_allow_nil(rel) + |> add_filter(rel) + + attribute -> + "" + |> add_destination_attribute(rel, "id") + |> add_source_attribute(rel, "#{rel.name}_id") + |> add_allow_nil(rel) + |> add_primary_key(attribute.primary_key?) + |> add_attribute_type(attribute) + |> add_filter(rel) end - end) - |> case do - [] -> - [] - - refs -> - refs - |> Enum.join("\n") - |> String.trim() - |> then( - &[ - """ - references do - #{&1} - end - """ - ] - ) end - end - defp add_match_with(str, empty) when empty in [[], nil], do: str + defp relationship_options(_spec, rel) do + default_destination_attribute = + rel.source + |> Module.split() + |> List.last() + |> Macro.underscore() + |> Kernel.<>("_id") + + "" + |> add_destination_attribute(rel, default_destination_attribute) + |> add_source_attribute(rel, "id") + |> add_filter(rel) + end - defp add_match_with(str, keyval), - do: str <> "\nmatch_with [#{Enum.map_join(keyval, fn {key, val} -> "#{key}: :#{val}" end)}]" + defp add_filter(str, %{match_with: []}), do: str - defp add_match_type(str, type) when type in ["SIMPLE", "NONE"], do: str + defp add_filter(str, %{match_with: match_with}) do + filter = + Enum.map_join(match_with, " and ", fn {source, dest} -> + "parent(#{source}) == #{dest}" + end) - defp add_match_type(str, "FULL"), do: str <> "\nmatch_type :full" - defp add_match_type(str, "PARTIAL"), do: str <> "\nmatch_type :partial" + "#{str}\n filter expr(#{filter})" + end - defp add_on(str, type, "RESTRICT"), do: str <> "\non_#{type} :restrict" - defp add_on(str, type, "CASCADE"), do: str <> "\non_#{type} :#{type}" - defp add_on(str, type, "SET NULL"), do: str <> "\non_#{type} :nilify" - defp add_on(str, _type, _), do: str + defp add_attribute_type(str, %{attr_type: :uuid}), do: str - defp custom_indexes(table_spec, true) do - table_spec.indexes - |> Enum.reject(fn index -> - !index.unique? || (&index_as_identity?/1) || - Enum.any?(index.columns, &String.contains?(&1, "(")) - end) - |> case do - [] -> - "" + defp add_attribute_type(str, %{attr_type: attr_type}) do + "#{str}\n attribute_type :#{attr_type}" + end - indexes -> - indexes - |> Enum.map_join(", ", fn %{index: name, columns: columns} -> - columns = Enum.map_join(columns, ", ", &":#{&1}") - "{[#{columns}], #{inspect(name)}}" - end) - |> then(fn index_names -> - "unique_index_names [#{index_names}]" - end) + defp add_destination_attribute(str, rel, default) do + if rel.destination_attribute == default do + str + else + "#{str}\n destination_attribute :#{rel.destination_attribute}" + end end - end - defp custom_indexes(table_spec, _) do - table_spec.indexes - |> Enum.reject(&index_as_identity?/1) - |> case do - [] -> - "" - - indexes -> - indexes - |> Enum.map_join("\n", fn index -> - columns = - index.columns - |> Enum.map_join(", ", fn thing -> - if String.contains?(thing, "(") do - inspect(thing) - else - Enum.at(String.split(":#{thing}", " "), 0) - end + defp add_source_attribute(str, rel, default) do + if rel.source_attribute == default do + str + else + "#{str}\n source_attribute :#{rel.source_attribute}" + end + end + + defp references(_table_spec, true) do + "" + end + + defp references(table_spec, _) do + table_spec.foreign_keys + |> Enum.flat_map(fn %Spec.ForeignKey{} = foreign_key -> + default_name = "#{table_spec.table_name}_#{foreign_key.column}_fkey" + + if default_name == foreign_key.constraint_name and + foreign_key.on_update == "NO ACTION" and + foreign_key.on_delete == "NO ACTION" and + foreign_key.match_type in ["SIMPLE", "NONE"] do + [] + else + relationship = + Enum.find(table_spec.relationships, fn relationship -> + relationship.type == :belongs_to and + relationship.constraint_name == foreign_key.constraint_name end) - case index_options(table_spec, index) do - "" -> - "index [#{columns}]" + if relationship do + relationship = relationship.name - options -> + options = + "" + |> add_on(:update, foreign_key.on_update) + |> add_on(:delete, foreign_key.on_delete) + |> add_match_with(foreign_key.match_with) + |> add_match_type(foreign_key.match_type) + + [ """ - index [#{columns}] do + reference :#{relationship} do #{options} end """ + ] + else + [] end - end) - |> then(fn indexes -> - """ - custom_indexes do - #{indexes} - end - """ - end) + end + end) + |> case do + [] -> + [] + + refs -> + refs + |> Enum.join("\n") + |> String.trim() + |> then( + &[ + """ + references do + #{&1} + end + """ + ] + ) + end end - end - defp index_as_identity?(index) do - is_nil(index.where_clause) and index.using == "btree" and index.include in [nil, []] and - Enum.all?(index.columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) - end + defp add_match_with(str, empty) when empty in [[], nil], do: str + + defp add_match_with(str, keyval), + do: str <> "\nmatch_with [#{Enum.map_join(keyval, fn {key, val} -> "#{key}: :#{val}" end)}]" + + defp add_match_type(str, type) when type in ["SIMPLE", "NONE"], do: str + + defp add_match_type(str, "FULL"), do: str <> "\nmatch_type :full" + defp add_match_type(str, "PARTIAL"), do: str <> "\nmatch_type :partial" + + defp add_on(str, type, "RESTRICT"), do: str <> "\non_#{type} :restrict" + defp add_on(str, type, "CASCADE"), do: str <> "\non_#{type} :#{type}" + defp add_on(str, type, "SET NULL"), do: str <> "\non_#{type} :nilify" + defp add_on(str, _type, _), do: str - defp index_options(spec, index) do - default_name = - if Enum.all?(index.columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) do - AshPostgres.CustomIndex.name(spec.table_name, %{fields: index.columns}) + defp custom_indexes(table_spec, true) do + table_spec.indexes + |> Enum.reject(fn index -> + !index.unique? || (&index_as_identity?/1) || + Enum.any?(index.columns, &String.contains?(&1, "(")) + end) + |> case do + [] -> + "" + + indexes -> + indexes + |> Enum.map_join(", ", fn %{index: name, columns: columns} -> + columns = Enum.map_join(columns, ", ", &":#{&1}") + "{[#{columns}], #{inspect(name)}}" + end) + |> then(fn index_names -> + "unique_index_names [#{index_names}]" + end) end + end - "" - |> add_index_name(index.name, default_name) - |> add_unique(index.unique?) - |> add_using(index.using) - |> add_where(index.where_clause) - |> add_include(index.include) - |> add_nulls_distinct(index.nulls_distinct) - end + defp custom_indexes(table_spec, _) do + table_spec.indexes + |> Enum.reject(&index_as_identity?/1) + |> case do + [] -> + "" + + indexes -> + indexes + |> Enum.map_join("\n", fn index -> + columns = + index.columns + |> Enum.map_join(", ", fn thing -> + if String.contains?(thing, "(") do + inspect(thing) + else + Enum.at(String.split(":#{thing}", " "), 0) + end + end) + + case index_options(table_spec, index) do + "" -> + "index [#{columns}]" + + options -> + """ + index [#{columns}] do + #{options} + end + """ + end + end) + |> then(fn indexes -> + """ + custom_indexes do + #{indexes} + end + """ + end) + end + end - defp add_index_name(str, default, default), do: str - defp add_index_name(str, name, _), do: str <> "\nname #{inspect(name)}" + defp index_as_identity?(index) do + is_nil(index.where_clause) and index.using == "btree" and index.include in [nil, []] and + Enum.all?(index.columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) + end - defp add_unique(str, false), do: str - defp add_unique(str, true), do: str <> "\nunique true" + defp index_options(spec, index) do + default_name = + if Enum.all?(index.columns, &Regex.match?(~r/^[0-9a-zA-Z_]+$/, &1)) do + AshPostgres.CustomIndex.name(spec.table_name, %{fields: index.columns}) + end - defp add_nulls_distinct(str, true), do: str - defp add_nulls_distinct(str, false), do: str <> "\nnulls_distinct false" + "" + |> add_index_name(index.name, default_name) + |> add_unique(index.unique?) + |> add_using(index.using) + |> add_where(index.where_clause) + |> add_include(index.include) + |> add_nulls_distinct(index.nulls_distinct) + end - defp add_using(str, "btree"), do: str - defp add_using(str, using), do: str <> "\nusing #{inspect(using)}" + defp add_index_name(str, default, default), do: str + defp add_index_name(str, name, _), do: str <> "\nname #{inspect(name)}" - defp add_where(str, empty) when empty in [nil, ""], do: str - defp add_where(str, where), do: str <> "\nwhere #{inspect(where)}" + defp add_unique(str, false), do: str + defp add_unique(str, true), do: str <> "\nunique true" - defp add_include(str, empty) when empty in [nil, []], do: str + defp add_nulls_distinct(str, true), do: str + defp add_nulls_distinct(str, false), do: str <> "\nnulls_distinct false" - defp add_include(str, include), - do: str <> "\ninclude [#{Enum.map_join(include, ", ", &inspect/1)}]" + defp add_using(str, "btree"), do: str + defp add_using(str, using), do: str <> "\nusing #{inspect(using)}" - defp attributes(table_spec) do - table_spec.attributes - |> Enum.split_with(& &1.default) - |> then(fn {l, r} -> r ++ l end) - |> Enum.split_with(& &1.primary_key?) - |> then(fn {l, r} -> l ++ r end) - |> Enum.filter(fn attribute -> - if not is_nil(attribute.default) or !!attribute.generated? or - attribute.source != attribute.name do - true - else - not Enum.any?(table_spec.relationships, fn relationship -> - relationship.type == :belongs_to and relationship.source_attribute == attribute.name - end) - end - end) - |> Enum.map_join("\n", &attribute(&1)) - end + defp add_where(str, empty) when empty in [nil, ""], do: str + defp add_where(str, where), do: str <> "\nwhere #{inspect(where)}" - defp attribute(attribute) do - now_default = &DateTime.utc_now/0 - uuid_default = &Ash.UUID.generate/0 - - {constructor, attribute, type?, type_option?} = - case attribute do - %{name: "updated_at", attr_type: attr_type} -> - {"update_timestamp", %{attribute | default: nil, generated?: false}, false, - attr_type != :utc_datetime_usec} - - %{default: default, attr_type: attr_type} - when default == now_default -> - {"create_timestamp", %{attribute | default: nil, generated?: false}, false, - attr_type != :utc_datetime_usec} - - %{default: default, attr_type: attr_type, primary_key?: true} - when default == uuid_default -> - {"uuid_primary_key", - %{attribute | default: nil, primary_key?: false, generated?: false, allow_nil?: true}, - false, attr_type != :uuid} - - _ -> - {"attribute", attribute, true, false} - end + defp add_include(str, empty) when empty in [nil, []], do: str + + defp add_include(str, include), + do: str <> "\ninclude [#{Enum.map_join(include, ", ", &inspect/1)}]" - case String.trim(options(attribute, type_option?)) do - "" -> - if type? do - "#{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)}" + defp attributes(table_spec) do + table_spec.attributes + |> Enum.split_with(& &1.default) + |> then(fn {l, r} -> r ++ l end) + |> Enum.split_with(& &1.primary_key?) + |> then(fn {l, r} -> l ++ r end) + |> Enum.filter(fn attribute -> + if not is_nil(attribute.default) or !!attribute.generated? or + attribute.source != attribute.name do + true else - "#{constructor} :#{attribute.name}" + not Enum.any?(table_spec.relationships, fn relationship -> + relationship.type == :belongs_to and relationship.source_attribute == attribute.name + end) end + end) + |> Enum.map_join("\n", &attribute(&1)) + end - options -> - if type? do - """ - #{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)} do - #{options} + defp attribute(attribute) do + now_default = &DateTime.utc_now/0 + uuid_default = &Ash.UUID.generate/0 + + {constructor, attribute, type?, type_option?} = + case attribute do + %{name: "updated_at", attr_type: attr_type} -> + {"update_timestamp", %{attribute | default: nil, generated?: false}, false, + attr_type != :utc_datetime_usec} + + %{default: default, attr_type: attr_type} + when default == now_default -> + {"create_timestamp", %{attribute | default: nil, generated?: false}, false, + attr_type != :utc_datetime_usec} + + %{default: default, attr_type: attr_type, primary_key?: true} + when default == uuid_default -> + {"uuid_primary_key", + %{ + attribute + | default: nil, + primary_key?: false, + generated?: false, + allow_nil?: true + }, false, attr_type != :uuid} + + _ -> + {"attribute", attribute, true, false} + end + + case String.trim(options(attribute, type_option?)) do + "" -> + if type? do + "#{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)}" + else + "#{constructor} :#{attribute.name}" end - """ - else - """ - #{constructor} :#{attribute.name} do - #{options} + + options -> + if type? do + """ + #{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)} do + #{options} + end + """ + else + """ + #{constructor} :#{attribute.name} do + #{options} + end + """ end - """ - end + end end - end - defp options(attribute, type_option?) do - "" - |> add_primary_key(attribute) - |> add_allow_nil(attribute) - |> add_sensitive(attribute) - |> add_default(attribute) - |> add_type(attribute, type_option?) - |> add_generated(attribute) - |> add_source(attribute) - end + defp options(attribute, type_option?) do + "" + |> add_primary_key(attribute) + |> add_allow_nil(attribute) + |> add_sensitive(attribute) + |> add_default(attribute) + |> add_type(attribute, type_option?) + |> add_generated(attribute) + |> add_source(attribute) + end - defp add_type(str, %{attr_type: attr_type}, true) do - str <> "\n type #{inspect(attr_type)}" - end + defp add_type(str, %{attr_type: attr_type}, true) do + str <> "\n type #{inspect(attr_type)}" + end - defp add_type(str, _, _), do: str + defp add_type(str, _, _), do: str - defp add_generated(str, %{generated?: true}) do - str <> "\n generated? true" - end + defp add_generated(str, %{generated?: true}) do + str <> "\n generated? true" + end - defp add_generated(str, _), do: str + defp add_generated(str, _), do: str - defp add_source(str, %{name: name, source: source}) when name != source do - str <> "\n source :#{source}" - end + defp add_source(str, %{name: name, source: source}) when name != source do + str <> "\n source :#{source}" + end - defp add_source(str, _), do: str + defp add_source(str, _), do: str - defp add_primary_key(str, %{primary_key?: true}) do - str <> "\n primary_key? true" - end + defp add_primary_key(str, %{primary_key?: true}) do + str <> "\n primary_key? true" + end - defp add_primary_key(str, _), do: str + defp add_primary_key(str, _), do: str - defp add_allow_nil(str, %{allow_nil?: false}) do - str <> "\n allow_nil? false" - end + defp add_allow_nil(str, %{allow_nil?: false}) do + str <> "\n allow_nil? false" + end - defp add_allow_nil(str, _), do: str + defp add_allow_nil(str, _), do: str - defp add_sensitive(str, %{sensitive?: true}) do - str <> "\n sensitive? true" - end + defp add_sensitive(str, %{sensitive?: true}) do + str <> "\n sensitive? true" + end - defp add_sensitive(str, _), do: str + defp add_sensitive(str, _), do: str - defp add_default(str, %{default: default}) when not is_nil(default) do - str <> "\n default #{inspect(default)}" - end + defp add_default(str, %{default: default}) when not is_nil(default) do + str <> "\n default #{inspect(default)}" + end - defp add_default(str, _), do: str + defp add_default(str, _), do: str + end end diff --git a/mix.exs b/mix.exs index e94bd4b7..be1f9f54 100644 --- a/mix.exs +++ b/mix.exs @@ -166,7 +166,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.4 and >= 3.4.44")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.40")}, - {:igniter, "~> 0.4 and >= 0.4.4"}, + {:igniter, "~> 0.4 and >= 0.4.4", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 353d3cdb..7141b79b 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -43,7 +43,7 @@ defmodule AshPostgresTest do end test "it does not run queries for exists/2 expressions that can be determined from loaded data" do - author = + author = AshPostgres.Test.Author |> Ash.Changeset.for_create(:create, %{}, authorize?: false) |> Ash.create!() @@ -55,7 +55,7 @@ defmodule AshPostgresTest do |> Ash.load!(:author) log = - capture_log(fn -> + capture_log(fn -> post |> Ash.Changeset.for_update(:update_if_author, %{title: "bad"}, authorize?: true, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index a3fe41b3..d9dfcdf9 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -81,7 +81,7 @@ defmodule AshPostgres.Test.Post do end bypass action(:update_if_author) do - authorize_if relates_to_actor_via(:author) + authorize_if(relates_to_actor_via(:author)) end policy action_type(:update) do From 056979ce9439734f647b98cb4ababcff60ebfa96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:28:00 -0500 Subject: [PATCH 0820/1215] chore(deps): bump ash in the production-dependencies group (#447) --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index bd21dc9a..bbea3320 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.46", "24286834d87719a8d9e0d1addf4b5be4c2acca30c554dbd5d66229d04748a15d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "78cd7d1d3ef27516f88a503e181c8e050f80d93222d76696d7491b200bd606db"}, + "ash": {:hex, :ash, "3.4.47", "3d5326b45fc264419347a387b0c9ccf9e032b4d1466aa7b62c737a94585fd232", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1e40a04efa8df5e1cbfc71b685ec846c8090a4b19a23be50450659ed259988e"}, "ash_sql": {:hex, :ash_sql, "0.2.41", "9e0a1686dc67a7cdc8435ced6c998dcd4de87980dde0a72d77946bbc9d1e65cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "226470dc8eeb3e89f98c0fb4ef11edf1b114e1caf3cd3457af7f9481df8221e8"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -33,12 +33,12 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, - "reactor": {:hex, :reactor, "0.10.2", "a9150cbada58e5331c5250c51c6a8c2d7c4d337919fc71c7dc188a7ae5b6de89", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5c46e71153f540b95e1a240365fc534daead9d19abe0d46eb819b7d715663484"}, + "reactor": {:hex, :reactor, "0.10.3", "41a8c34251148e36dd7c75aa8433f2c2f283f29c097f9eb84a630ab28dd75651", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b34380e22b69a35943a7bcceffd5a8b766870f1fc9052162a7ff74ef9cdb3b2"}, "rewrite": {:hex, :rewrite, "1.1.1", "0e6674eb5f8cb11aabe5ad6207151b4156bf173aa9b43133a68f8cc882364570", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "fcd688b3ca543c3a1f1f4615ccc054ec37cfcde91133a27a683ec09b35ae1496"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.35", "1c0bb30f340151eca24164885935de39e6ada4010555f444c813d0488990f8f3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f242d6385c287389034a0e146d8f025b5c9ab777f1ae5cf0fdfc9209db6ae748"}, + "spark": {:hex, :spark, "2.2.36", "07c921e5efb27f184267c3431d2f82099e24cac90748a47383dd75cbfb558268", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e5ac56b75e5ad43da6d8302b6713277488f8e9a3abdba9aae8f0d0f9cff04538"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From d2657c1ffb720abf3d610e8d27f3c1d715cab043 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 19 Dec 2024 16:07:13 -0500 Subject: [PATCH 0821/1215] fix: handle double select issue chore: update for 1.18 --- .tool-versions | 2 +- lib/data_layer.ex | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.tool-versions b/.tool-versions index 307762be..cab727e0 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 27.0.1 -elixir 1.18.0-rc.0-otp-27 +elixir 1.18.0-otp-27 diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 480f644f..2e7cc0ea 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1109,6 +1109,7 @@ defmodule AshPostgres.DataLayer do data_layer_query = data_layer_query |> Ecto.Query.exclude(:distinct) + |> Ecto.Query.exclude(:select) if query.__ash_bindings__[:__order__?] do {:ok, @@ -1149,10 +1150,12 @@ defmodule AshPostgres.DataLayer do case lateral_join_source_query(query, source_query) do {:ok, data_layer_query} -> + data_layer_query = Ecto.Query.exclude(data_layer_query, :select) + through_resource |> Ash.Query.new() |> Ash.Query.put_context(:data_layer, %{ - start_bindings_at: data_layer_query.__ash_bindings__.current + start_bindings_at: Map.get(data_layer_query, :__ash_bindings__)[:current] }) |> Ash.Query.set_context(through_relationship.context) |> Ash.Query.do_filter(through_relationship.filter) @@ -1168,6 +1171,7 @@ defmodule AshPostgres.DataLayer do end |> case do {:ok, through_query} -> + through_query = Ecto.Query.exclude(through_query, :select) if query.__ash_bindings__[:__order__?] do subquery = subquery( @@ -1180,7 +1184,7 @@ defmodule AshPostgres.DataLayer do source_query, relationship.through ), - as: ^data_layer_query.__ash_bindings__.current, + as: ^Map.get(data_layer_query, :__ash_bindings__)[:current], on: field(through, ^destination_attribute_on_join_resource) == field(destination, ^destination_attribute), @@ -1197,7 +1201,6 @@ defmodule AshPostgres.DataLayer do ) ) - data_layer_query = Ecto.Query.exclude(data_layer_query, :distinct) {:ok, from(source in data_layer_query, @@ -1220,7 +1223,7 @@ defmodule AshPostgres.DataLayer do source_query, relationship.through ), - as: ^data_layer_query.__ash_bindings__.current, + as: ^Map.get(data_layer_query, :__ash_bindings__)[:current], on: field(through, ^destination_attribute_on_join_resource) == field(destination, ^destination_attribute), From 16fe1f15b421046fcec6d6d11c9903d8799b941f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 19 Dec 2024 16:26:41 -0500 Subject: [PATCH 0822/1215] chore: upgrade igniter --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index bbea3320..5de55445 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.4.8", "6d1bf4934952ac3eb20f6cbac0d5cd6d8012e42e3de20ad794703556c14cfa08", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f9dd06f971fa053c6b0d9f8263b625f619a0fd3645d6a8cd6170935055a8f0df"}, + "igniter": {:hex, :igniter, "0.5.0", "00984e515c4bcd0aa0cffec83612e19041295f834392c4426e9cb0a67de16eb1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4cbb8acc908343bf79d90697d593b9d1bbce49ae72a0d755664d4cfe897dba53"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 410da84a669632256de4780b569437932c278036 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 19 Dec 2024 16:27:52 -0500 Subject: [PATCH 0823/1215] chore: format --- lib/data_layer.ex | 4 ++-- lib/migration_generator/migration_generator.ex | 6 +++--- lib/multitenancy.ex | 2 +- lib/repo/before_compile.ex | 2 +- lib/verifiers/validate_references.ex | 2 +- mix.exs | 1 + test/support/repo_case.ex | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 2e7cc0ea..4614dd44 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1171,7 +1171,8 @@ defmodule AshPostgres.DataLayer do end |> case do {:ok, through_query} -> - through_query = Ecto.Query.exclude(through_query, :select) + through_query = Ecto.Query.exclude(through_query, :select) + if query.__ash_bindings__[:__order__?] do subquery = subquery( @@ -1201,7 +1202,6 @@ defmodule AshPostgres.DataLayer do ) ) - {:ok, from(source in data_layer_query, where: field(source, ^source_attribute) in ^source_values, diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 6c58be70..3f3dd5bf 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -177,7 +177,7 @@ defmodule AshPostgres.MigrationGenerator do |> Path.join(repo_name) |> Path.join("extensions.json") - unless opts.dry_run || opts.check do + if !(opts.dry_run || opts.check) do File.rename(legacy_snapshot_file, snapshot_file) end @@ -1027,7 +1027,7 @@ defmodule AshPostgres.MigrationGenerator do end defp create_new_snapshot(snapshots, repo_name, opts, tenant?) do - unless opts.dry_run do + if !opts.dry_run do Enum.each(snapshots, fn snapshot -> snapshot_binary = snapshot_to_binary(snapshot) @@ -2868,7 +2868,7 @@ defmodule AshPostgres.MigrationGenerator do configured_reference = configured_reference(resource, table, attribute.source || attribute.name, relationship) - unless Map.get(configured_reference, :ignore?) do + if !Map.get(configured_reference, :ignore?) do destination_attribute = Ash.Resource.Info.attribute( relationship.destination, diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index fbae77fe..505c9cb0 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -90,7 +90,7 @@ defmodule AshPostgres.MultiTenancy do end defp validate_tenant_name!(tenant_name) do - unless Regex.match?(@tenant_name_regex, tenant_name) do + if !Regex.match?(@tenant_name_regex, tenant_name) do raise "Tenant name must match #{inspect(@tenant_name_regex)}, got: #{tenant_name}" end end diff --git a/lib/repo/before_compile.ex b/lib/repo/before_compile.ex index a53f96d0..c2e9a310 100644 --- a/lib/repo/before_compile.ex +++ b/lib/repo/before_compile.ex @@ -3,7 +3,7 @@ defmodule AshPostgres.Repo.BeforeCompile do defmacro __before_compile__(_env) do quote do - unless Module.defines?(__MODULE__, {:min_pg_version, 0}, :def) do + if !Module.defines?(__MODULE__, {:min_pg_version, 0}, :def) do IO.warn(""" Please define `min_pg_version/0` in repo module: #{inspect(__MODULE__)} diff --git a/lib/verifiers/validate_references.ex b/lib/verifiers/validate_references.ex index 0bb6955c..3f2c90aa 100644 --- a/lib/verifiers/validate_references.ex +++ b/lib/verifiers/validate_references.ex @@ -7,7 +7,7 @@ defmodule AshPostgres.Verifiers.ValidateReferences do dsl |> AshPostgres.DataLayer.Info.references() |> Enum.each(fn reference -> - unless Ash.Resource.Info.relationship(dsl, reference.relationship) do + if !Ash.Resource.Info.relationship(dsl, reference.relationship) do raise Spark.Error.DslError, path: [:postgres, :references, reference.relationship], module: Verifier.get_persisted(dsl, :module), diff --git a/mix.exs b/mix.exs index be1f9f54..ec2ef870 100644 --- a/mix.exs +++ b/mix.exs @@ -237,6 +237,7 @@ defmodule AshPostgres.MixProject do "spark.replace_doc_links", "spark.cheat_sheets_in_search" ], + format: "format --migrate", "spark.formatter": "spark.formatter --extensions AshPostgres.DataLayer", "spark.cheat_sheets": "spark.cheat_sheets --extensions AshPostgres.DataLayer", "spark.cheat_sheets_in_search": diff --git a/test/support/repo_case.ex b/test/support/repo_case.ex index f4b535c9..28d9f3d2 100644 --- a/test/support/repo_case.ex +++ b/test/support/repo_case.ex @@ -19,7 +19,7 @@ defmodule AshPostgres.RepoCase do setup tags do :ok = Sandbox.checkout(AshPostgres.TestRepo) - unless tags[:async] do + if !tags[:async] do Sandbox.mode(AshPostgres.TestRepo, {:shared, self()}) end From e47cf93a00d93bcb2b3cad0ebec9ceceee8fa979 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 20 Dec 2024 11:30:53 -0500 Subject: [PATCH 0824/1215] chore: release version v2.4.18 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 4 ++-- mix.lock | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af41d627..32279517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.18](https://github.com/ash-project/ash_postgres/compare/v2.4.17...v2.4.18) (2024-12-20) + + + + +### Bug Fixes: + +* handle double select issue + +### Improvements: + +* make igniter optional + +* make tsvector type selectable + ## [v2.4.17](https://github.com/ash-project/ash_postgres/compare/v2.4.16...v2.4.17) (2024-12-16) diff --git a/mix.exs b/mix.exs index ec2ef870..cfddc3bd 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.17" + @version "2.4.18" def project do [ @@ -164,7 +164,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.44")}, + {:ash, ash_version("~> 3.4 and >= 3.4.48")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.40")}, {:igniter, "~> 0.4 and >= 0.4.4", optional: true}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index 5de55445..87c21283 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.47", "3d5326b45fc264419347a387b0c9ccf9e032b4d1466aa7b62c737a94585fd232", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1e40a04efa8df5e1cbfc71b685ec846c8090a4b19a23be50450659ed259988e"}, + "ash": {:hex, :ash, "3.4.48", "e2948ca59a97305f49155dda9ebe647465bdc6524cffcf2afb1d7a62904d7eb1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0086400da603a70ef5ceaee6a3cb817cb345e077bf94c0308255f2094617c5b2"}, "ash_sql": {:hex, :ash_sql, "0.2.41", "9e0a1686dc67a7cdc8435ced6c998dcd4de87980dde0a72d77946bbc9d1e65cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "226470dc8eeb3e89f98c0fb4ef11edf1b114e1caf3cd3457af7f9481df8221e8"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From 7e0c2053a1dc7f0732c78d5f3e41ce4e3ef58504 Mon Sep 17 00:00:00 2001 From: Alex Slade Date: Wed, 25 Dec 2024 01:13:09 +0000 Subject: [PATCH 0825/1215] bug: add search path to postgres functions (#449) --- lib/migration_generator/ash_functions.ex | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 369173cb..53b29ac9 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -10,6 +10,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ LANGUAGE SQL + SET search_path = '' IMMUTABLE; \"\"\") @@ -17,6 +18,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ SELECT COALESCE($1, $2) $$ LANGUAGE SQL + SET search_path = '' IMMUTABLE; \"\"\") @@ -27,6 +29,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do ELSE $1 END $$ LANGUAGE SQL + SET search_path = '' IMMUTABLE; \"\"\") @@ -37,6 +40,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do ELSE $1 END $$ LANGUAGE SQL + SET search_path = '' IMMUTABLE; \"\"\") @@ -62,6 +66,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do END IF; END; $$ LANGUAGE plpgsql + SET search_path = '' IMMUTABLE; \"\"\") @@ -115,6 +120,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do END IF; END; $$ LANGUAGE plpgsql + SET search_path = '' IMMUTABLE; \"\"\") """ @@ -177,7 +183,8 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do RAISE EXCEPTION '#{prefix}%', json_data::text; RETURN NULL; END; - $$ LANGUAGE plpgsql; + $$ LANGUAGE plpgsql + SET search_path = ''; \"\"\") execute(\"\"\" @@ -189,7 +196,8 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do RAISE EXCEPTION '#{prefix}%', json_data::text; RETURN NULL; END; - $$ LANGUAGE plpgsql; + $$ LANGUAGE plpgsql + SET search_path = ''; \"\"\") """ end @@ -220,6 +228,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do END $$ LANGUAGE PLPGSQL + SET search_path = '' VOLATILE; \"\"\") @@ -230,6 +239,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); $$ LANGUAGE SQL + SET search_path = '' IMMUTABLE PARALLEL SAFE STRICT; \"\"\") """ From 3da10a19b311d02cb1e120c8c6f83046eddae4ae Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 25 Dec 2024 01:25:36 -0500 Subject: [PATCH 0826/1215] improvement: automatically set `min_pg_version` where possible improvement: use a notice to suggest configuring `min_pg_version` doing this instead of showing a prompt, to reduce initial sign-up friction fixes #430 --- lib/igniter.ex | 29 +++++++++++---- lib/mix/tasks/ash_postgres.install.ex | 53 ++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 75b636e6..2e00bb7b 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -3,8 +3,8 @@ if Code.ensure_loaded?(Igniter) do @moduledoc "Codemods and utilities for working with AshPostgres & Igniter" @doc false - def default_repo_contents(otp_app, name, opts \\ []) do - min_pg_version = get_min_pg_version(name, opts) + def default_repo_contents(otp_app, opts \\ []) do + min_pg_version = opts[:min_pg_version] || Version.parse!("16.0.0") """ use AshPostgres.Repo, otp_app: #{inspect(otp_app)} @@ -88,12 +88,25 @@ if Code.ensure_loaded?(Igniter) do otp_app = Igniter.Project.Application.app_name(igniter) igniter = - Igniter.Project.Module.create_module( - igniter, - repo, - default_repo_contents(otp_app, repo, opts), - opts - ) + if opts[:min_pg_version] do + igniter + else + Igniter.add_notice(igniter, """ + A `min_pg_version/0` function has been defined + in `#{inspect(repo)}` as `16.0.0`. + + You may wish to update this configuration. It should + be set to the lowest version that your application + expects to be run against. + """) + end + + Igniter.Project.Module.create_module( + igniter, + repo, + default_repo_contents(otp_app, opts), + opts + ) {igniter, repo} else diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index d59e31dd..d8942193 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -362,11 +362,40 @@ if Code.ensure_loaded?(Igniter) do end ) else + min_pg_version = get_min_pg_version() + + notice = + if min_pg_version do + """ + A `min_pg_version/0` function has been defined + in `#{inspect(repo)}` as `#{min_pg_version}`. + + This was based on running `postgres -V`. + + You may wish to update this configuration. It should + be set to the lowest version that your application + expects to be run against. + """ + else + """ + A `min_pg_version/0` function has been defined in + `#{inspect(repo)}` automatically. + + You may wish to update this configuration. It should + be set to the lowest version that your application + expects to be run against. + """ + end + Igniter.Project.Module.create_module( igniter, repo, - AshPostgres.Igniter.default_repo_contents(otp_app, repo, opts) + AshPostgres.Igniter.default_repo_contents( + otp_app, + Keyword.put(opts, :min_pg_version, min_pg_version) + ) ) + |> Igniter.add_notice(notice) end |> Igniter.Project.Module.find_and_update_module!( repo, @@ -382,6 +411,28 @@ if Code.ensure_loaded?(Igniter) do ) end + def get_min_pg_version do + case System.cmd("postgres", ["-V"]) do + {"postgres (PostgreSQL) " <> version_and_text, 0} -> + version_and_text + |> String.split(~r/\s+/, parts: 2, trim: true) + |> Enum.at(0) + |> String.split(".", trim: true) + |> case do + [major, minor, patch | _] -> Version.parse!("#{major}.#{minor}.#{patch}") + [major, minor] -> Version.parse!("#{major}.#{minor}.0") + [major] -> Version.parse!("#{major}.0.0") + _ -> nil + end + + _ -> + nil + end + rescue + _ -> + nil + end + defp use_ash_postgres_instead_of_ecto(zipper) do with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Ecto.Repo), {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Ecto.Repo), From a630f4b854c72247dc576790514c07d1f239d6bc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 25 Dec 2024 02:25:57 -0500 Subject: [PATCH 0827/1215] improvement: better min_pg_version when modifying a repo --- lib/mix/tasks/ash_postgres.install.ex | 79 ++++++++++++++++----------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index d8942193..460637bb 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -362,30 +362,7 @@ if Code.ensure_loaded?(Igniter) do end ) else - min_pg_version = get_min_pg_version() - - notice = - if min_pg_version do - """ - A `min_pg_version/0` function has been defined - in `#{inspect(repo)}` as `#{min_pg_version}`. - - This was based on running `postgres -V`. - - You may wish to update this configuration. It should - be set to the lowest version that your application - expects to be run against. - """ - else - """ - A `min_pg_version/0` function has been defined in - `#{inspect(repo)}` automatically. - - You may wish to update this configuration. It should - be set to the lowest version that your application - expects to be run against. - """ - end + {min_pg_version, notice} = min_pg_version_and_notice(repo) Igniter.Project.Module.create_module( igniter, @@ -405,13 +382,53 @@ if Code.ensure_loaded?(Igniter) do repo, &configure_prefer_transaction_function/1 ) - |> Igniter.Project.Module.find_and_update_module!( - repo, - &configure_min_pg_version_function(&1, repo, opts) - ) + |> then(fn igniter -> + {min_pg_version, notice} = min_pg_version_and_notice(repo) + + igniter + |> Igniter.Project.Module.find_and_update_module!( + repo, + &configure_min_pg_version_function( + &1, + repo, + min_pg_version || Version.parse!("16.0.0"), + opts + ) + ) + |> Igniter.add_notice(notice) + end) + end + + defp min_pg_version_and_notice(repo) do + min_pg_version = get_min_pg_version() + + notice = + if min_pg_version do + """ + A `min_pg_version/0` function has been defined + in `#{inspect(repo)}` as `#{min_pg_version}`. + + This was based on running `postgres -V`. + + You may wish to update this configuration. It should + be set to the lowest version that your application + expects to be run against. + """ + else + """ + A `min_pg_version/0` function has been defined in + `#{inspect(repo)}` automatically. + + You may wish to update this configuration. It should + be set to the lowest version that your application + expects to be run against. + """ + end + + {min_pg_version, notice} end - def get_min_pg_version do + defp get_min_pg_version do case System.cmd("postgres", ["-V"]) do {"postgres (PostgreSQL) " <> version_and_text, 0} -> version_and_text @@ -519,13 +536,13 @@ if Code.ensure_loaded?(Igniter) do end end - defp configure_min_pg_version_function(zipper, repo, opts) do + defp configure_min_pg_version_function(zipper, repo, version, opts) do case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do {:ok, zipper} -> {:ok, zipper} _ -> - min_pg_version = AshPostgres.Igniter.get_min_pg_version(repo, opts) + min_pg_version = get_min_pg_version() {:ok, Igniter.Code.Common.add_code(zipper, """ From 39033fb5a2bac87a63ff73474845a076e5f646f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 10:42:27 -0500 Subject: [PATCH 0828/1215] chore(deps): bump the production-dependencies group with 3 updates (#450) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.48 to 3.4.49 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.48...v3.4.49) Updates `ash_sql` from 0.2.41 to 0.2.42 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.41...v0.2.42) Updates `igniter` from 0.5.0 to 0.5.2 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.0...v0.5.2) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 87c21283..a0589999 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.48", "e2948ca59a97305f49155dda9ebe647465bdc6524cffcf2afb1d7a62904d7eb1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0086400da603a70ef5ceaee6a3cb817cb345e077bf94c0308255f2094617c5b2"}, - "ash_sql": {:hex, :ash_sql, "0.2.41", "9e0a1686dc67a7cdc8435ced6c998dcd4de87980dde0a72d77946bbc9d1e65cb", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "226470dc8eeb3e89f98c0fb4ef11edf1b114e1caf3cd3457af7f9481df8221e8"}, + "ash": {:hex, :ash, "3.4.49", "85d1e115058ce6dbd9111b66ed55e2425dc0cfda874b60ed3f49299996326a72", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef2573a43b035e2c3bdee614522a33845f9904d07c519cac801972102f5ca057"}, + "ash_sql": {:hex, :ash_sql, "0.2.42", "01c160f1cbcce2bad534fd8372a3ce04b1b88bb9348c59aab6755f76963f444e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "75ca2d3265b529b2441d0dc300a0b18c20a18f766a1d7cc97cf5904ce3bd9813"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.5.0", "00984e515c4bcd0aa0cffec83612e19041295f834392c4426e9cb0a67de16eb1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4cbb8acc908343bf79d90697d593b9d1bbce49ae72a0d755664d4cfe897dba53"}, + "igniter": {:hex, :igniter, "0.5.2", "0fd8aacbb5b80c27a0cf2e9b66fe2b633f181f759ba6bbcdc3ae758cef146f1d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3a5c5085d4dfa1c23c53b25b891b9b56e9b2ba6a7ef281c0baee12bff17bf4d0"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 452a8208dd81057e9bcae19dfcbaccc34113a52a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Dec 2024 12:22:04 -0500 Subject: [PATCH 0829/1215] fix: ensure there is always at least one upsert field so filter is run --- lib/data_layer.ex | 18 ++++++++++-------- lib/mix/tasks/ash_postgres.install.ex | 2 +- mix.exs | 2 +- mix.lock | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 4614dd44..7fb3dd0a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1383,7 +1383,7 @@ defmodule AshPostgres.DataLayer do ecto_changeset.changes, [] ) do - :empty -> + {:empty, query} -> if options[:return_records?] do if changeset.context[:data_layer][:use_atomic_update_data?] do case query.__ash_bindings__ do @@ -1821,12 +1821,8 @@ defmodule AshPostgres.DataLayer do %{}, upsert_set ) do - :empty -> - if options[:return_records?] do - {:replace, options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)} - else - :nothing - end + {:empty, _query} -> + raise "Cannot upsert with no fields to specify in the upsert statement. This can only happen on resources without a primary key." {:ok, query} -> query @@ -1967,7 +1963,13 @@ defmodule AshPostgres.DataLayer do fields_to_upsert = upsert_fields -- - (Keyword.keys(Enum.at(changesets, 0).atomics) -- keys) + Keyword.keys(Enum.at(changesets, 0).atomics) -- keys + + fields_to_upsert = + case fields_to_upsert do + [] -> keys + fields_to_upsert -> fields_to_upsert + end fields_to_upsert |> Enum.uniq() diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 460637bb..60a3b6a7 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -536,7 +536,7 @@ if Code.ensure_loaded?(Igniter) do end end - defp configure_min_pg_version_function(zipper, repo, version, opts) do + defp configure_min_pg_version_function(zipper, _repo, _version, _opts) do case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do {:ok, zipper} -> {:ok, zipper} diff --git a/mix.exs b/mix.exs index cfddc3bd..e4e95809 100644 --- a/mix.exs +++ b/mix.exs @@ -165,7 +165,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.48")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.40")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.43")}, {:igniter, "~> 0.4 and >= 0.4.4", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index a0589999..1d165cab 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.49", "85d1e115058ce6dbd9111b66ed55e2425dc0cfda874b60ed3f49299996326a72", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef2573a43b035e2c3bdee614522a33845f9904d07c519cac801972102f5ca057"}, - "ash_sql": {:hex, :ash_sql, "0.2.42", "01c160f1cbcce2bad534fd8372a3ce04b1b88bb9348c59aab6755f76963f444e", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "75ca2d3265b529b2441d0dc300a0b18c20a18f766a1d7cc97cf5904ce3bd9813"}, + "ash_sql": {:hex, :ash_sql, "0.2.43", "80579d708630d3d31e9bb74b74d16ff8ef70de8c1d5043d87a682b5b17cb3154", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3fc710129178c017002bd108fc9411b10ca1d276fa5e99bd01b99addf28ff42"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, From a3edddde8b564056772d00d3e821136576f5e609 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Dec 2024 12:23:00 -0500 Subject: [PATCH 0830/1215] chore: release version v2.4.19 --- CHANGELOG.md | 17 +++++++++++++++++ mix.exs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32279517..be5782a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.19](https://github.com/ash-project/ash_postgres/compare/v2.4.18...v2.4.19) (2024-12-26) + + + + +### Bug Fixes: + +* ensure there is always at least one upsert field so filter is run + +### Improvements: + +* better min_pg_version when modifying a repo + +* automatically set `min_pg_version` where possible + +* use a notice to suggest configuring `min_pg_version` + ## [v2.4.18](https://github.com/ash-project/ash_postgres/compare/v2.4.17...v2.4.18) (2024-12-20) diff --git a/mix.exs b/mix.exs index e4e95809..e9ea8826 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.18" + @version "2.4.19" def project do [ From 80ccc19a919419f71498891817a0a86ef9d7e67b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Dec 2024 15:23:09 -0500 Subject: [PATCH 0831/1215] fix: use passed in version of postgres when modifying existing repo --- lib/mix/tasks/ash_postgres.install.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 60a3b6a7..636fe999 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -536,18 +536,16 @@ if Code.ensure_loaded?(Igniter) do end end - defp configure_min_pg_version_function(zipper, _repo, _version, _opts) do + defp configure_min_pg_version_function(zipper, _repo, version, _opts) do case Igniter.Code.Function.move_to_def(zipper, :min_pg_version, 0) do {:ok, zipper} -> {:ok, zipper} _ -> - min_pg_version = get_min_pg_version() - {:ok, Igniter.Code.Common.add_code(zipper, """ def min_pg_version do - %Version{major: #{min_pg_version.major}, minor: #{min_pg_version.minor}, patch: #{min_pg_version.patch}} + %Version{major: #{version.major}, minor: #{version.minor}, patch: #{version.patch}} end """)} end From 2dadf5be25fa9f50ad84ac9751120f05e0b5ff15 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 26 Dec 2024 15:23:30 -0500 Subject: [PATCH 0832/1215] chore: release version v2.4.20 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be5782a1..b8a557fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.20](https://github.com/ash-project/ash_postgres/compare/v2.4.19...v2.4.20) (2024-12-26) + + + + +### Bug Fixes: + +* use passed in version of postgres when modifying existing repo + ## [v2.4.19](https://github.com/ash-project/ash_postgres/compare/v2.4.18...v2.4.19) (2024-12-26) diff --git a/mix.exs b/mix.exs index e9ea8826..17fb306e 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.19" + @version "2.4.20" def project do [ From ab7f9b054c69bf1b1cc0a9734ec3235d4ea7df79 Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Sun, 29 Dec 2024 19:44:24 +0800 Subject: [PATCH 0833/1215] docs: Fix old code in Getting Started guide (#451) --- documentation/tutorials/get-started-with-ash-postgres.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index 12d88328..8e02c72a 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -241,7 +241,7 @@ for i <- 0..5 do ticket = Helpdesk.Support.Ticket |> Ash.Changeset.for_create(:open, %{subject: "Issue #{i}"}) - |> Helpdesk.Support.create!() + |> Ash.create!() |> Ash.Changeset.for_update(:assign, %{representative_id: representative.id}) |> Ash.update!() From 9701d83c03bf49cb254a4c75aac6f40f55384200 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 07:52:43 -0500 Subject: [PATCH 0834/1215] chore(deps): bump the production-dependencies group with 2 updates (#453) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.49 to 3.4.50 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.49...v3.4.50) Updates `igniter` from 0.5.2 to 0.5.3 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.2...v0.5.3) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 1d165cab..e50ef9c6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.49", "85d1e115058ce6dbd9111b66ed55e2425dc0cfda874b60ed3f49299996326a72", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef2573a43b035e2c3bdee614522a33845f9904d07c519cac801972102f5ca057"}, + "ash": {:hex, :ash, "3.4.50", "1dadf1ac9302893c16276c613891e01e6ab205782c4dc2b3b82609f5032073d9", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "055cc3260b73c1c9dc584a0cdb94820fdd228faeb9551020750d9af18f4bb75d"}, "ash_sql": {:hex, :ash_sql, "0.2.43", "80579d708630d3d31e9bb74b74d16ff8ef70de8c1d5043d87a682b5b17cb3154", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3fc710129178c017002bd108fc9411b10ca1d276fa5e99bd01b99addf28ff42"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.5.2", "0fd8aacbb5b80c27a0cf2e9b66fe2b633f181f759ba6bbcdc3ae758cef146f1d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3a5c5085d4dfa1c23c53b25b891b9b56e9b2ba6a7ef281c0baee12bff17bf4d0"}, + "igniter": {:hex, :igniter, "0.5.3", "7cd26c707df380d7f1e3d1969b911d1485f582d3fc5f76a6d39d5ca94e039a71", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3a22a74e8cc4af02fd89b0a6ea0184f22764ddb254ec31c194e89839ad22baf4"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -34,7 +34,7 @@ "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, "reactor": {:hex, :reactor, "0.10.3", "41a8c34251148e36dd7c75aa8433f2c2f283f29c097f9eb84a630ab28dd75651", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b34380e22b69a35943a7bcceffd5a8b766870f1fc9052162a7ff74ef9cdb3b2"}, - "rewrite": {:hex, :rewrite, "1.1.1", "0e6674eb5f8cb11aabe5ad6207151b4156bf173aa9b43133a68f8cc882364570", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "fcd688b3ca543c3a1f1f4615ccc054ec37cfcde91133a27a683ec09b35ae1496"}, + "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, From 304a896ec82deca43fe720b455fdddccdd30dcfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 07:52:59 -0500 Subject: [PATCH 0835/1215] chore(deps-dev): bump credo in the dev-dependencies group (#454) Bumps the dev-dependencies group with 1 update: [credo](https://github.com/rrrene/credo). Updates `credo` from 1.7.10 to 1.7.11 - [Release notes](https://github.com/rrrene/credo/releases) - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/compare/v1.7.10...v1.7.11) --- updated-dependencies: - dependency-name: credo dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index e50ef9c6..442cf03b 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,7 @@ "ash_sql": {:hex, :ash_sql, "0.2.43", "80579d708630d3d31e9bb74b74d16ff8ef70de8c1d5043d87a682b5b17cb3154", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3fc710129178c017002bd108fc9411b10ca1d276fa5e99bd01b99addf28ff42"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, + "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, From a5f6de785df0cb41fc523a1018672c6fabb64a63 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 2 Jan 2025 15:01:11 -0500 Subject: [PATCH 0836/1215] fix: don't use symlinked app dir for migration's path fixes #452 --- lib/data_layer.ex | 2 +- lib/migration_generator/migration_generator.ex | 15 +++++---------- lib/mix/helpers.ex | 13 ++++++++----- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 7fb3dd0a..07deed32 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1963,7 +1963,7 @@ defmodule AshPostgres.DataLayer do fields_to_upsert = upsert_fields -- - Keyword.keys(Enum.at(changesets, 0).atomics) -- keys + (Keyword.keys(Enum.at(changesets, 0).atomics) -- keys) fields_to_upsert = case fields_to_upsert do diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 3f3dd5bf..004df533 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -157,11 +157,7 @@ defmodule AshPostgres.MigrationGenerator do snapshot_path else app = Keyword.fetch!(config, :otp_app) - - Application.app_dir( - app, - ["priv", "resource_snapshots"] - ) + Path.join(Mix.Project.deps_paths()[app] || File.cwd!(), "priv/resource_snapshots") end end @@ -889,25 +885,24 @@ defmodule AshPostgres.MigrationGenerator do defp migration_path(opts, repo, tenant? \\ false) do # Copied from ecto's mix task, thanks Ecto ❤️ config = repo.config() - app = Keyword.fetch!(config, :otp_app) if tenant? do if path = opts.tenant_migration_path || config[:tenant_migrations_path] do path else priv = - config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + AshPostgres.Mix.Helpers.source_repo_priv(repo) - Application.app_dir(app, Path.join(priv, "tenant_migrations")) + Path.join(priv, "tenant_migrations") end else if path = opts.migration_path || config[:tenant_migrations_path] do path else priv = - config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + AshPostgres.Mix.Helpers.source_repo_priv(repo) - Application.app_dir(app, Path.join(priv, "migrations")) + Path.join(priv, "migrations") end end end diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 6858ec47..26abfabd 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -170,16 +170,19 @@ defmodule AshPostgres.Mix.Helpers do end def derive_migrations_path(repo) do - config = repo.config() - priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" - app = Keyword.fetch!(config, :otp_app) - Application.app_dir(app, Path.join(priv, "migrations")) + priv = source_repo_priv(repo) + Path.join(priv, "migrations") end def derive_tenant_migrations_path(repo) do + priv = source_repo_priv(repo) + Path.join(priv, "tenant_migrations") + end + + def source_repo_priv(repo) do config = repo.config() priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" app = Keyword.fetch!(config, :otp_app) - Application.app_dir(app, Path.join(priv, "tenant_migrations")) + Path.join(Mix.Project.deps_paths()[app] || File.cwd!(), priv) end end From 1c4ae7bed69d0e1318167ed5c90cd8b1eeb77e77 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 3 Jan 2025 10:19:59 -0500 Subject: [PATCH 0837/1215] fix: filter query by source record ids when lateral joining --- lib/data_layer.ex | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 07deed32..1f7d0c49 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1059,7 +1059,7 @@ defmodule AshPostgres.DataLayer do source_pkey = Ash.Resource.Info.primary_key(source_query.resource) - case lateral_join_source_query(query, source_query) do + case lateral_join_source_query(query, source_query, root_data) do {:ok, data_layer_query} -> source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) @@ -1148,7 +1148,7 @@ defmodule AshPostgres.DataLayer do source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) source_pkey = Ash.Resource.Info.primary_key(source_query.resource) - case lateral_join_source_query(query, source_query) do + case lateral_join_source_query(query, source_query, root_data) do {:ok, data_layer_query} -> data_layer_query = Ecto.Query.exclude(data_layer_query, :select) @@ -1268,7 +1268,8 @@ defmodule AshPostgres.DataLayer do lateral_join_source_query: lateral_join_source_query } }, - source_query + source_query, + _root_data ) when not is_nil(lateral_join_source_query) do {:ok, @@ -1276,10 +1277,11 @@ defmodule AshPostgres.DataLayer do |> set_subquery_prefix(source_query, lateral_join_source_query.__ash_bindings__.resource)} end - defp lateral_join_source_query(query, source_query) do + defp lateral_join_source_query(query, source_query, root_data) do source_query.resource |> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]}) |> Ash.Query.set_tenant(source_query.tenant) + |> filter_for_records(root_data) |> set_lateral_join_prefix(query) |> case do %{valid?: true} = query -> @@ -1290,6 +1292,38 @@ defmodule AshPostgres.DataLayer do end end + defp filter_for_records(query, records) do + keys = + case Ash.Resource.Info.primary_key(query.resource) do + [] -> + case Ash.Resource.Info.identities(query.resource) do + %{keys: keys} -> keys + _ -> [] + end + + pkey -> + pkey + end + + expr = + case keys do + [] -> + raise "Cannot use lateral joins with a resource that has no primary key and no identities" + + keys -> + Enum.reduce(records, Ash.Expr.expr(false), fn record, filter_expr -> + all_keys_match_expr = + Enum.reduce(keys, Ash.Expr.expr(true), fn key, key_expr -> + Ash.Expr.expr(^key_expr and ^Ash.Expr.ref(key) == ^Map.get(record, key)) + end) + + Ash.Expr.expr(^filter_expr or ^all_keys_match_expr) + end) + end + + Ash.Query.do_filter(query, expr) + end + @doc false def set_subquery_prefix(data_layer_query, source_query, resource) do repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) From 96dc292fa80cce00fade890e2bd7ab9f1082fab8 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Sat, 4 Jan 2025 05:26:27 -0600 Subject: [PATCH 0838/1215] chore: add changelog to package links (#455) --- mix.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/mix.exs b/mix.exs index 17fb306e..b6f10848 100644 --- a/mix.exs +++ b/mix.exs @@ -59,6 +59,7 @@ defmodule AshPostgres.MixProject do files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG* documentation), links: %{ + Changelog: "https://hexdocs.pm/ash_postgres/changelog.html", GitHub: "https://github.com/ash-project/ash_postgres" } ] From 5f26d6900915e2a8f988fe469beaa631606a84e3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 6 Jan 2025 17:07:01 -0500 Subject: [PATCH 0839/1215] chore: release version v2.4.21 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a557fe..4c49243f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.21](https://github.com/ash-project/ash_postgres/compare/v2.4.20...v2.4.21) (2025-01-06) + + + + +### Bug Fixes: + +* filter query by source record ids when lateral joining + +* don't use symlinked app dir for migration's path + ## [v2.4.20](https://github.com/ash-project/ash_postgres/compare/v2.4.19...v2.4.20) (2024-12-26) diff --git a/mix.exs b/mix.exs index b6f10848..bd364740 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.20" + @version "2.4.21" def project do [ From fbc434689223180ed3cc7077732913c587811802 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Wed, 8 Jan 2025 08:05:52 -0600 Subject: [PATCH 0840/1215] test: Failing test for module calculation inside expression one (#456) --- .../test_repo/comedians/20241217232254.json | 59 +++++++++++ .../test_repo/jokes/20241217232254.json | 98 +++++++++++++++++++ .../20241217232254_migrate_resources45.exs | 77 +++++++++++++++ test/calculation_test.exs | 8 +- test/support/domain.ex | 2 + test/support/resources/comedian.ex | 57 +++++++++++ test/support/resources/joke.ex | 28 ++++++ 7 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/comedians/20241217232254.json create mode 100644 priv/resource_snapshots/test_repo/jokes/20241217232254.json create mode 100644 priv/test_repo/migrations/20241217232254_migrate_resources45.exs create mode 100644 test/support/resources/comedian.ex create mode 100644 test/support/resources/joke.ex diff --git a/priv/resource_snapshots/test_repo/comedians/20241217232254.json b/priv/resource_snapshots/test_repo/comedians/20241217232254.json new file mode 100644 index 00000000..ed9e2fd8 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comedians/20241217232254.json @@ -0,0 +1,59 @@ +{ + "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?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "A376A922D4610DEAD0294DD863E8E2FF72FEC6316AC6B809C3DFC17B29094EBA", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "comedians" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/jokes/20241217232254.json b/priv/resource_snapshots/test_repo/jokes/20241217232254.json new file mode 100644 index 00000000..2393183f --- /dev/null +++ b/priv/resource_snapshots/test_repo/jokes/20241217232254.json @@ -0,0 +1,98 @@ +{ + "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": "text", + "type": "text" + }, + { + "allow_nil?": true, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "is_good", + "type": "boolean" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "jokes_comedian_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "comedians" + }, + "size": null, + "source": "comedian_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "CFDCBDBAC925B20A1EA8EB4B3C37ACF1FD915FA21F6B07DE4D066AA167B9EEF5", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "jokes" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20241217232254_migrate_resources45.exs b/priv/test_repo/migrations/20241217232254_migrate_resources45.exs new file mode 100644 index 00000000..acfc2b39 --- /dev/null +++ b/priv/test_repo/migrations/20241217232254_migrate_resources45.exs @@ -0,0 +1,77 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources45 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 + create table(:jokes, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:text, :text) + add(:is_good, :boolean, default: false) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:comedian_id, :uuid) + end + + create table(:comedians, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:jokes) do + modify( + :comedian_id, + references(:comedians, + column: :id, + name: "jokes_comedian_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:comedians) do + add(:name, :text) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + end + + def down do + alter table(:comedians) do + remove(:updated_at) + remove(:inserted_at) + remove(:name) + end + + drop(constraint(:jokes, "jokes_comedian_id_fkey")) + + alter table(:jokes) do + modify(:comedian_id, :uuid) + end + + drop(table(:comedians)) + + drop(table(:jokes)) + end +end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index fd0ab43f..f1a091a2 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.CalculationTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Account, Author, Comment, Post, User} + alias AshPostgres.Test.{Account, Author, Comedian, Comment, Post, User} require Ash.Query import Ash.Expr @@ -937,6 +937,12 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + test "module calculation inside expr calculation works" do + commedian = Comedian.create!(%{name: "John"}) + commedian = Ash.get!(Comedian, commedian.id, load: [:has_jokes_expr], authorize?: false) + assert %{has_jokes_expr: false} = commedian + end + def fred do "fred" end diff --git a/test/support/domain.ex b/test/support/domain.ex index 60d2275a..f690211a 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -5,6 +5,7 @@ defmodule AshPostgres.Test.Domain do resources do resource(AshPostgres.Test.CoAuthorPost) resource(AshPostgres.Test.Post) + resource(AshPostgres.Test.Comedian) resource(AshPostgres.Test.Comment) resource(AshPostgres.Test.IntegerPost) resource(AshPostgres.Test.Rating) @@ -14,6 +15,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Profile) resource(AshPostgres.Test.User) resource(AshPostgres.Test.Invite) + resource(AshPostgres.Test.Joke) resource(AshPostgres.Test.Account) resource(AshPostgres.Test.Organization) resource(AshPostgres.Test.Manager) diff --git a/test/support/resources/comedian.ex b/test/support/resources/comedian.ex new file mode 100644 index 00000000..1fa08c54 --- /dev/null +++ b/test/support/resources/comedian.ex @@ -0,0 +1,57 @@ +defmodule AshPostgres.Test.Comedian do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + create_timestamp(:inserted_at, public?: true) + update_timestamp(:updated_at, public?: true) + end + + relationships do + has_many(:jokes, AshPostgres.Test.Joke, public?: true) + end + + calculations do + calculate(:has_jokes_mod, :boolean, AshPostgres.Test.Comedian.HasJokes) + calculate(:has_jokes_expr, :boolean, expr(has_jokes_mod == true)) + end + + actions do + defaults([:read]) + + create :create do + primary?(true) + accept([:name]) + end + end + + code_interface do + define(:create) + end + + postgres do + table("comedians") + repo(AshPostgres.TestRepo) + end +end + +defmodule AshPostgres.Test.Comedian.HasJokes do + use Ash.Resource.Calculation + + @impl true + def load(_, _, _) do + [:jokes] + end + + @impl true + def calculate(comedians, _, _) do + Enum.map(comedians, fn %{jokes: jokes} -> + Enum.any?(jokes) + end) + end +end diff --git a/test/support/resources/joke.ex b/test/support/resources/joke.ex new file mode 100644 index 00000000..a97b1e3f --- /dev/null +++ b/test/support/resources/joke.ex @@ -0,0 +1,28 @@ +defmodule AshPostgres.Test.Joke do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + attributes do + uuid_primary_key(:id) + attribute(:text, :string, public?: true) + attribute(:is_good, :boolean, default: false, public?: true) + create_timestamp(:inserted_at, public?: true) + update_timestamp(:updated_at, public?: true) + end + + relationships do + belongs_to(:comedian, AshPostgres.Test.Comedian, public?: true) + end + + actions do + defaults([:read]) + end + + postgres do + table("jokes") + repo(AshPostgres.TestRepo) + end +end From 2b5b3bd505d0ecace94a7aba149ff22dc21a99fd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 11 Jan 2025 19:42:12 -0500 Subject: [PATCH 0841/1215] fix: fully specificy synthesized indices from multi-resource tables --- lib/migration_generator/migration_generator.ex | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 004df533..2810ceb8 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -814,7 +814,11 @@ defmodule AshPostgres.MigrationGenerator do %{ keys: pkey_names, - name: name + name: name, + base_filter: nil, + all_tenants?: false, + nils_distinct?: false, + where: nil } end) @@ -849,7 +853,11 @@ defmodule AshPostgres.MigrationGenerator do %{ keys: pkey_names, - name: name + name: name, + base_filter: nil, + all_tenants?: false, + nils_distinct?: false, + where: nil } end) From 696bbe73a69c882caccd257c60e547b4b64e7330 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Jan 2025 08:31:21 -0500 Subject: [PATCH 0842/1215] fix: inner join bulk operations if distinct? is present --- lib/data_layer.ex | 4 +++- mix.lock | 6 +++--- test/bulk_destroy_test.exs | 10 ++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 1f7d0c49..6a524453 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1560,7 +1560,7 @@ defmodule AshPostgres.DataLayer do end) needs_to_join? = - requires_adding_inner_join? || + requires_adding_inner_join? || query.distinct || query.limit || query.offset || has_exists? query = @@ -1730,6 +1730,8 @@ defmodule AshPostgres.DataLayer do Ecto.Query.exclude(query, :select) end + # query = Ecto.Query.exclude(query, :distinct) + {_, results} = with_savepoint(repo, query, fn -> repo.delete_all( diff --git a/mix.lock b/mix.lock index 442cf03b..2a858f30 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.50", "1dadf1ac9302893c16276c613891e01e6ab205782c4dc2b3b82609f5032073d9", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "055cc3260b73c1c9dc584a0cdb94820fdd228faeb9551020750d9af18f4bb75d"}, + "ash": {:hex, :ash, "3.4.55", "81132171412dc92bd1630500e1403a48f5ea7948552c528189d13d4c4c181878", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6fb89c9bb6e158b2dab08184f3b6ebfd88785348c21b2a9fd5bd71b1f96e2c5e"}, "ash_sql": {:hex, :ash_sql, "0.2.43", "80579d708630d3d31e9bb74b74d16ff8ef70de8c1d5043d87a682b5b17cb3154", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3fc710129178c017002bd108fc9411b10ca1d276fa5e99bd01b99addf28ff42"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -21,7 +21,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.5.3", "7cd26c707df380d7f1e3d1969b911d1485f582d3fc5f76a6d39d5ca94e039a71", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3a22a74e8cc4af02fd89b0a6ea0184f22764ddb254ec31c194e89839ad22baf4"}, + "igniter": {:hex, :igniter, "0.5.8", "d91e90fecb99beadfa9d0d8434fbd4f0fe06ea1a1d29cae4dfd0cb058cb3a5c7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fef198324925405ea5c3b16166002be03b2d7497c038cfc9708aa557d27ba5a2"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,7 +39,7 @@ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, "spark": {:hex, :spark, "2.2.36", "07c921e5efb27f184267c3431d2f82099e24cac90748a47383dd75cbfb558268", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e5ac56b75e5ad43da6d8302b6713277488f8e9a3abdba9aae8f0d0f9cff04538"}, - "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, + "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index ddc9b65a..ff159a8f 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -60,6 +60,16 @@ defmodule AshPostgres.BulkDestroyTest do assert [%{title: "george"}] = Ash.read!(Post) end + test "the query can join to to-many related tables when necessary" do + Ash.bulk_create!([%{title: "fred"}, %{title: "fred"}], Post, :create) + + Post + |> Ash.Query.filter(posts_with_matching_title.title == title) + |> Ash.bulk_destroy!(:destroy, %{}, return_records?: true) + + assert [] = Ash.read!(Post) + end + test "bulk destroys can be done even on stream inputs" do Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) From 8e59757864ce65c6581f6fdb779332291d5c06b4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Jan 2025 08:32:16 -0500 Subject: [PATCH 0843/1215] chore: release version v2.4.22 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c49243f..93357fd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.4.22](https://github.com/ash-project/ash_postgres/compare/v2.4.21...v2.4.22) (2025-01-13) + + + + +### Bug Fixes: + +* inner join bulk operations if distinct? is present + +* fully specificy synthesized indices from multi-resource tables + ## [v2.4.21](https://github.com/ash-project/ash_postgres/compare/v2.4.20...v2.4.21) (2025-01-06) diff --git a/mix.exs b/mix.exs index bd364740..06279f90 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.21" + @version "2.4.22" def project do [ From 1d0437a675b883ae10298d72b758a8b5b67356cc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Jan 2025 16:12:19 -0500 Subject: [PATCH 0844/1215] improvement: mark ash_raise_error as STABLE --- lib/migration_generator/ash_functions.ex | 25 +++++++- .../test_no_sandbox_repo/extensions.json | 2 +- .../test_repo/extensions.json | 2 +- ...3205301_migrate_resources_extensions_1.exs | 60 +++++++++++++++++++ ...3205259_migrate_resources_extensions_1.exs | 60 +++++++++++++++++++ 5 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs create mode 100644 priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 53b29ac9..a9c4659c 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -1,5 +1,5 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do - @latest_version 4 + @latest_version 5 def latest_version, do: @latest_version @@ -143,7 +143,26 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do end def install(3) do - uuid_generate_v7() + """ + execute("ALTER FUNCTION ash_raise_error(jsonb) STABLE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) STABLE") + #{uuid_generate_v7()} + """ + end + + def install(4) do + """ + execute("ALTER FUNCTION ash_raise_error(jsonb) STABLE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) STABLE") + #{uuid_generate_v7()} + """ + end + + def drop(4) do + """ + execute("ALTER FUNCTION ash_raise_error(jsonb) VOLATILE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) VOLATILE") + """ end def drop(3) do @@ -184,6 +203,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do RETURN NULL; END; $$ LANGUAGE plpgsql + STABLE SET search_path = ''; \"\"\") @@ -197,6 +217,7 @@ defmodule AshPostgres.MigrationGenerator.AshFunctions do RETURN NULL; END; $$ LANGUAGE plpgsql + STABLE SET search_path = ''; \"\"\") """ diff --git a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json index 40d20b77..4430c306 100644 --- a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json +++ b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json @@ -1,5 +1,5 @@ { - "ash_functions_version": 4, + "ash_functions_version": 5, "installed": [ "ash-functions", "uuid-ossp", diff --git a/priv/resource_snapshots/test_repo/extensions.json b/priv/resource_snapshots/test_repo/extensions.json index 557d0ea8..d1c5a122 100644 --- a/priv/resource_snapshots/test_repo/extensions.json +++ b/priv/resource_snapshots/test_repo/extensions.json @@ -1,5 +1,5 @@ { - "ash_functions_version": 4, + "ash_functions_version": 5, "installed": [ "ash-functions", "uuid-ossp", diff --git a/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs b/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs new file mode 100644 index 00000000..9794c3be --- /dev/null +++ b/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs @@ -0,0 +1,60 @@ +defmodule AshPostgres.TestNoSandboxRepo.Migrations.MigrateResourcesExtensions1 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute("ALTER FUNCTION ash_raise_error(jsonb) STABLE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) STABLE") + + execute(""" + CREATE OR REPLACE FUNCTION uuid_generate_v7() + RETURNS UUID + AS $$ + DECLARE + timestamp TIMESTAMPTZ; + microseconds INT; + BEGIN + timestamp = clock_timestamp(); + microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; + + RETURN encode( + set_byte( + set_byte( + overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 + ), + 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int + ), + 7, microseconds::bit(8)::int + ), + 'hex')::UUID; + END + $$ + LANGUAGE PLPGSQL + SET search_path = '' + VOLATILE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) + RETURNS TIMESTAMP WITHOUT TIME ZONE + AS $$ + SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); + $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE PARALLEL SAFE STRICT; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute("ALTER FUNCTION ash_raise_error(jsonb) VOLATILE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) VOLATILE") + end +end diff --git a/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs b/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs new file mode 100644 index 00000000..e6efdcb9 --- /dev/null +++ b/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs @@ -0,0 +1,60 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResourcesExtensions1 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute("ALTER FUNCTION ash_raise_error(jsonb) STABLE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) STABLE") + + execute(""" + CREATE OR REPLACE FUNCTION uuid_generate_v7() + RETURNS UUID + AS $$ + DECLARE + timestamp TIMESTAMPTZ; + microseconds INT; + BEGIN + timestamp = clock_timestamp(); + microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; + + RETURN encode( + set_byte( + set_byte( + overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 + ), + 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int + ), + 7, microseconds::bit(8)::int + ), + 'hex')::UUID; + END + $$ + LANGUAGE PLPGSQL + SET search_path = '' + VOLATILE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) + RETURNS TIMESTAMP WITHOUT TIME ZONE + AS $$ + SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); + $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE PARALLEL SAFE STRICT; + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute("ALTER FUNCTION ash_raise_error(jsonb) VOLATILE;") + execute("ALTER FUNCTION ash_raise_error(jsonb, ANYCOMPATIBLE) VOLATILE") + end +end From 1ff16563800ae5e9a36aae33807d40356971248d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Jan 2025 17:03:41 -0500 Subject: [PATCH 0845/1215] improvement: add `c:AshPostgres.Repo.default_constraint_match_type` --- lib/data_layer.ex | 114 ++++++++++++++++++++++++++++++---------------- lib/repo.ex | 16 +++++++ 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6a524453..a69851cc 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1375,6 +1375,8 @@ defmodule AshPostgres.DataLayer do @impl true def update_query(query, changeset, resource, options) do + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + ecto_changeset = case changeset.data do %Ash.Changeset.OriginalDataNotAvailable{} -> @@ -1384,7 +1386,7 @@ defmodule AshPostgres.DataLayer do data end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :update, true) + |> ecto_changeset(changeset, :update, repo, true) case bulk_updatable_query( query, @@ -1398,8 +1400,6 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> try do - repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - repo_opts = AshSql.repo_opts( repo, @@ -1673,6 +1673,8 @@ defmodule AshPostgres.DataLayer do @impl true def destroy_query(query, changeset, resource, options) do + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + ecto_changeset = case changeset.data do %Ash.Changeset.OriginalDataNotAvailable{} -> @@ -1682,7 +1684,7 @@ defmodule AshPostgres.DataLayer do data end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :delete, true) + |> ecto_changeset(changeset, :delete, repo, true) case bulk_updatable_query( query, @@ -1697,8 +1699,6 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> try do - repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - repo_opts = AshSql.repo_opts( repo, @@ -1926,7 +1926,7 @@ defmodule AshPostgres.DataLayer do handle_raised_error( e, __STACKTRACE__, - {:bulk_create, ecto_changeset(changeset.data, changeset, :create, false)}, + {:bulk_create, ecto_changeset(changeset.data, changeset, :create, repo, false)}, resource ) end @@ -2142,7 +2142,7 @@ defmodule AshPostgres.DataLayer do ) end - defp ecto_changeset(record, changeset, type, table_error?) do + defp ecto_changeset(record, changeset, type, repo, table_error?) do attributes = changeset.resource |> Ash.Resource.Info.attributes() @@ -2159,24 +2159,24 @@ defmodule AshPostgres.DataLayer do |> set_table(changeset, type, table_error?) |> Ecto.Changeset.cast(%{}, []) |> force_changes(Map.take(changeset.attributes, attributes_to_change)) - |> add_configured_foreign_key_constraints(record.__struct__) - |> add_unique_indexes(record.__struct__, changeset) - |> add_check_constraints(record.__struct__) - |> add_exclusion_constraints(record.__struct__) + |> add_configured_foreign_key_constraints(record.__struct__, repo) + |> add_unique_indexes(record.__struct__, changeset, repo) + |> add_check_constraints(record.__struct__, repo) + |> add_exclusion_constraints(record.__struct__, repo) case type do :create -> ecto_changeset - |> add_my_foreign_key_constraints(record.__struct__) + |> add_my_foreign_key_constraints(record.__struct__, repo) type when type in [:upsert, :update] -> ecto_changeset - |> add_my_foreign_key_constraints(record.__struct__) - |> add_related_foreign_key_constraints(record.__struct__) + |> add_my_foreign_key_constraints(record.__struct__, repo) + |> add_related_foreign_key_constraints(record.__struct__, repo) :delete -> ecto_changeset - |> add_related_foreign_key_constraints(record.__struct__) + |> add_related_foreign_key_constraints(record.__struct__, repo) end end @@ -2461,7 +2461,7 @@ defmodule AshPostgres.DataLayer do def to_ecto(other), do: other - defp add_check_constraints(changeset, resource) do + defp add_check_constraints(changeset, resource, repo) do resource |> AshPostgres.DataLayer.Info.check_constraints() |> Enum.reduce(changeset, fn constraint, changeset -> @@ -2470,27 +2470,35 @@ defmodule AshPostgres.DataLayer do |> Enum.reduce(changeset, fn attribute, changeset -> Ecto.Changeset.check_constraint(changeset, attribute, name: constraint.name, - message: constraint.message || "is invalid" + message: constraint.message || "is invalid", + match: repo.default_constraint_match_type(:check, constraint.name) ) end) end) end - defp add_exclusion_constraints(changeset, resource) do + defp add_exclusion_constraints(changeset, resource, repo) do resource |> AshPostgres.DataLayer.Info.exclusion_constraint_names() |> Enum.reduce(changeset, fn constraint, changeset -> case constraint do {key, name} -> - Ecto.Changeset.exclusion_constraint(changeset, key, name: name) + Ecto.Changeset.exclusion_constraint(changeset, key, + name: name, + match: repo.default_constraint_match_type(:exclusion, constraint.name) + ) {key, name, message} -> - Ecto.Changeset.exclusion_constraint(changeset, key, name: name, message: message) + Ecto.Changeset.exclusion_constraint(changeset, key, + name: name, + message: message, + match: repo.default_constraint_match_type(:exclusion, constraint.name) + ) end end) end - defp add_related_foreign_key_constraints(changeset, resource) do + defp add_related_foreign_key_constraints(changeset, resource, repo) do # TODO: this doesn't guarantee us to get all of them, because if something is related to this # schema and there is no back-relation, then this won't catch it's foreign key constraints resource @@ -2514,25 +2522,37 @@ defmodule AshPostgres.DataLayer do %{name: name} when not is_nil(name) -> Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, name: name, + match: repo.default_constraint_match_type(:foreign, name), message: "would leave records behind" ) _ -> + name = "#{AshPostgres.DataLayer.Info.table(source)}_#{source_attribute}_fkey" + Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, - name: "#{AshPostgres.DataLayer.Info.table(source)}_#{source_attribute}_fkey", + name: name, + match: repo.default_constraint_match_type(:foreign, name), message: "would leave records behind" ) end end) end - defp add_my_foreign_key_constraints(changeset, resource) do + defp add_my_foreign_key_constraints(changeset, resource, repo) do resource |> Ash.Resource.Info.relationships() - |> Enum.reduce(changeset, &Ecto.Changeset.foreign_key_constraint(&2, &1.source_attribute)) + |> Enum.reduce(changeset, fn relationship, changeset -> + name = + "#{AshPostgres.DataLayer.Info.table(resource)}_#{relationship.source_attribute}_fkey" + + Ecto.Changeset.foreign_key_constraint(changeset, relationship.source_attribute, + name: name, + match: repo.default_constraint_match_type(:foreign, name) + ) + end) end - defp add_configured_foreign_key_constraints(changeset, resource) do + defp add_configured_foreign_key_constraints(changeset, resource, repo) do resource |> AshPostgres.DataLayer.Info.foreign_key_names() |> case do @@ -2541,14 +2561,21 @@ defmodule AshPostgres.DataLayer do end |> Enum.reduce(changeset, fn {key, name}, changeset -> - Ecto.Changeset.foreign_key_constraint(changeset, key, name: name) + Ecto.Changeset.foreign_key_constraint(changeset, key, + name: name, + match: repo.default_constraint_match_type(:foreign, name) + ) {key, name, message}, changeset -> - Ecto.Changeset.foreign_key_constraint(changeset, key, name: name, message: message) + Ecto.Changeset.foreign_key_constraint(changeset, key, + name: name, + message: message, + match: repo.default_constraint_match_type(:foreign, name) + ) end) end - defp add_unique_indexes(changeset, resource, ash_changeset) do + defp add_unique_indexes(changeset, resource, ash_changeset, repo) do table = table(resource, ash_changeset) pkey = Ash.Resource.Info.primary_key(resource) @@ -2560,11 +2587,13 @@ defmodule AshPostgres.DataLayer do AshPostgres.DataLayer.Info.identity_index_names(resource)[identity.name] || "#{table}_#{identity.name}_index" + index_match_type = repo.default_constraint_match_type(:unique, name) + opts = if Map.get(identity, :message) do - [name: name, message: identity.message] + [name: name, message: identity.message, match: index_match_type] else - [name: name] + [name: name, match: index_match_type] end fields = @@ -2585,11 +2614,13 @@ defmodule AshPostgres.DataLayer do |> Enum.reduce(changeset, fn index, changeset -> name = index.name || AshPostgres.CustomIndex.name(table, index) + index_match_type = repo.default_constraint_match_type(:custom, name) + opts = if index.message do - [name: name, message: index.message] + [name: name, message: index.message, match: index_match_type] else - [name: name] + [name: name, match: index_match_type] end fields = @@ -2631,10 +2662,17 @@ defmodule AshPostgres.DataLayer do Enum.reduce(names, changeset, fn {keys, name}, changeset -> - Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), name: name) + Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), + name: name, + match: repo.default_constraint_match_type(:unique, name) + ) {keys, name, message}, changeset -> - Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), name: name, message: message) + Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), + name: name, + message: message, + match: repo.default_constraint_match_type(:unique, name) + ) end) end @@ -2917,6 +2955,8 @@ defmodule AshPostgres.DataLayer do ) |> pkey_filter(record) + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) + with {:ok, query} <- filter(query, changeset.filter, resource) do ecto_changeset = case changeset.data do @@ -2927,7 +2967,7 @@ defmodule AshPostgres.DataLayer do data end |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) - |> ecto_changeset(changeset, :delete, true) + |> ecto_changeset(changeset, :delete, repo, true) case bulk_updatable_query( query, @@ -2942,8 +2982,6 @@ defmodule AshPostgres.DataLayer do {:ok, query} -> try do - repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) - repo_opts = AshSql.repo_opts( repo, diff --git a/lib/repo.ex b/lib/repo.ex index 5429fdf6..a40cfd16 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -84,6 +84,19 @@ defmodule AshPostgres.Repo do @doc "Whether or not to explicitly start and close a transaction for each atomic update action, even if there are no transaction hooks. Defaults to `false`." @callback prefer_transaction_for_atomic_updates?() :: boolean + @doc """ + Determine how constraint names are matched when generating errors. + + This is useful if you are using something like citus that creates generated constraint + names for each node. In that case, for example, you might return a regex that + matches the name plus digits. + """ + @callback default_constraint_match_type( + type :: :custom | :exclusion | :unique | :foreign | :check, + name :: String.t() + ) :: + :exact | :prefix | :suffix | {:regex, Regex.t()} + @doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields" @callback override_migration_type(atom) :: atom @doc "Should the repo should be created by `mix ash_postgres.create`?" @@ -126,6 +139,8 @@ defmodule AshPostgres.Repo do def prefer_transaction_for_atomic_updates?, do: false + def default_constraint_match_type(_type, _name), do: :exact + def transaction!(fun) do case fun.() do {:ok, value} -> value @@ -266,6 +281,7 @@ defmodule AshPostgres.Repo do on_transaction_begin: 1, installed_extensions: 0, all_tenants: 0, + default_constraint_match_type: 2, prefer_transaction?: 0, prefer_transaction_for_atomic_updates?: 0, tenant_migrations_path: 0, From 12f914dc9913b07f31fc1c566e48b144c678deab Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 14 Jan 2025 13:18:08 +0100 Subject: [PATCH 0846/1215] fix: handle regex match correctly (#460) --- lib/data_layer.ex | 228 +++++++++++++++++++++-------- test/support/resources/comedian.ex | 1 + 2 files changed, 171 insertions(+), 58 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a69851cc..40723180 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2468,11 +2468,21 @@ defmodule AshPostgres.DataLayer do constraint.attribute |> List.wrap() |> Enum.reduce(changeset, fn attribute, changeset -> - Ecto.Changeset.check_constraint(changeset, attribute, - name: constraint.name, - message: constraint.message || "is invalid", - match: repo.default_constraint_match_type(:check, constraint.name) - ) + case repo.default_constraint_match_type(:check, constraint.name) do + {:regex, regex} -> + Ecto.Changeset.check_constraint(changeset, attribute, + name: regex, + message: constraint.message || "is invalid", + match: :exact + ) + + match -> + Ecto.Changeset.check_constraint(changeset, attribute, + name: constraint.name, + message: constraint.message || "is invalid", + match: match + ) + end end) end) end @@ -2483,17 +2493,36 @@ defmodule AshPostgres.DataLayer do |> Enum.reduce(changeset, fn constraint, changeset -> case constraint do {key, name} -> - Ecto.Changeset.exclusion_constraint(changeset, key, - name: name, - match: repo.default_constraint_match_type(:exclusion, constraint.name) - ) + case repo.default_constraint_match_type(:check, name) do + {:regex, regex} -> + Ecto.Changeset.check_constraint(changeset, key, + name: regex, + match: :exact + ) + + match -> + Ecto.Changeset.check_constraint(changeset, key, + name: name, + match: match + ) + end {key, name, message} -> - Ecto.Changeset.exclusion_constraint(changeset, key, - name: name, - message: message, - match: repo.default_constraint_match_type(:exclusion, constraint.name) - ) + case repo.default_constraint_match_type(:check, name) do + {:regex, regex} -> + Ecto.Changeset.check_constraint(changeset, key, + name: regex, + message: message, + match: :exact + ) + + match -> + Ecto.Changeset.check_constraint(changeset, key, + name: name, + message: message, + match: match + ) + end end end) end @@ -2520,20 +2549,40 @@ defmodule AshPostgres.DataLayer do changeset -> case AshPostgres.DataLayer.Info.reference(resource, relationship_name) do %{name: name} when not is_nil(name) -> - Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, - name: name, - match: repo.default_constraint_match_type(:foreign, name), - message: "would leave records behind" - ) + case repo.default_constraint_match_type(:foreign, name) do + {:regex, regex} -> + Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, + name: regex, + message: "would leave records behind", + match: :exact + ) + + match -> + Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, + name: name, + message: "would leave records behind", + match: match + ) + end _ -> name = "#{AshPostgres.DataLayer.Info.table(source)}_#{source_attribute}_fkey" - Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, - name: name, - match: repo.default_constraint_match_type(:foreign, name), - message: "would leave records behind" - ) + case repo.default_constraint_match_type(:foreign, name) do + {:regex, regex} -> + Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, + name: regex, + message: "would leave records behind", + match: :exact + ) + + match -> + Ecto.Changeset.foreign_key_constraint(changeset, destination_attribute, + name: name, + message: "would leave records behind", + match: match + ) + end end end) end @@ -2545,10 +2594,19 @@ defmodule AshPostgres.DataLayer do name = "#{AshPostgres.DataLayer.Info.table(resource)}_#{relationship.source_attribute}_fkey" - Ecto.Changeset.foreign_key_constraint(changeset, relationship.source_attribute, - name: name, - match: repo.default_constraint_match_type(:foreign, name) - ) + case repo.default_constraint_match_type(:foreign, name) do + {:regex, regex} -> + Ecto.Changeset.foreign_key_constraint(changeset, relationship.source_attribute, + name: regex, + match: :exact + ) + + match -> + Ecto.Changeset.foreign_key_constraint(changeset, relationship.source_attribute, + name: name, + match: match + ) + end end) end @@ -2561,17 +2619,36 @@ defmodule AshPostgres.DataLayer do end |> Enum.reduce(changeset, fn {key, name}, changeset -> - Ecto.Changeset.foreign_key_constraint(changeset, key, - name: name, - match: repo.default_constraint_match_type(:foreign, name) - ) + case repo.default_constraint_match_type(:foreign, name) do + {:regex, regex} -> + Ecto.Changeset.foreign_key_constraint(changeset, key, + name: regex, + match: :exact + ) + + match -> + Ecto.Changeset.foreign_key_constraint(changeset, key, + name: name, + match: match + ) + end {key, name, message}, changeset -> - Ecto.Changeset.foreign_key_constraint(changeset, key, - name: name, - message: message, - match: repo.default_constraint_match_type(:foreign, name) - ) + case repo.default_constraint_match_type(:foreign, name) do + {:regex, regex} -> + Ecto.Changeset.foreign_key_constraint(changeset, key, + name: regex, + message: message, + match: :exact + ) + + match -> + Ecto.Changeset.foreign_key_constraint(changeset, key, + name: name, + message: message, + match: match + ) + end end) end @@ -2587,13 +2664,21 @@ defmodule AshPostgres.DataLayer do AshPostgres.DataLayer.Info.identity_index_names(resource)[identity.name] || "#{table}_#{identity.name}_index" - index_match_type = repo.default_constraint_match_type(:unique, name) - opts = - if Map.get(identity, :message) do - [name: name, message: identity.message, match: index_match_type] - else - [name: name, match: index_match_type] + case repo.default_constraint_match_type(:unique, name) do + {:regex, regex} -> + if Map.get(identity, :message) do + [name: regex, message: identity.message, match: :exact] + else + [name: regex, match: :exact] + end + + index_match_type -> + if Map.get(identity, :message) do + [name: name, message: identity.message, match: index_match_type] + else + [name: name, match: index_match_type] + end end fields = @@ -2614,13 +2699,21 @@ defmodule AshPostgres.DataLayer do |> Enum.reduce(changeset, fn index, changeset -> name = index.name || AshPostgres.CustomIndex.name(table, index) - index_match_type = repo.default_constraint_match_type(:custom, name) - opts = - if index.message do - [name: name, message: index.message, match: index_match_type] - else - [name: name, match: index_match_type] + case repo.default_constraint_match_type(:custom, name) do + {:regex, regex} -> + if Map.get(index, :message) do + [name: regex, message: index.message, match: :exact] + else + [name: regex, match: :exact] + end + + index_match_type -> + if Map.get(index, :message) do + [name: name, message: index.message, match: index_match_type] + else + [name: name, match: index_match_type] + end end fields = @@ -2662,17 +2755,36 @@ defmodule AshPostgres.DataLayer do Enum.reduce(names, changeset, fn {keys, name}, changeset -> - Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), - name: name, - match: repo.default_constraint_match_type(:unique, name) - ) + case repo.default_constraint_match_type(:unique, name) do + {:regex, regex} -> + Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), + name: regex, + match: :exact + ) + + match -> + Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), + name: name, + match: match + ) + end {keys, name, message}, changeset -> - Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), - name: name, - message: message, - match: repo.default_constraint_match_type(:unique, name) - ) + case repo.default_constraint_match_type(:unique, name) do + {:regex, regex} -> + Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), + name: regex, + message: message, + match: :exact + ) + + match -> + Ecto.Changeset.unique_constraint(changeset, List.wrap(keys), + name: name, + message: message, + match: match + ) + end end) end diff --git a/test/support/resources/comedian.ex b/test/support/resources/comedian.ex index 1fa08c54..a210270c 100644 --- a/test/support/resources/comedian.ex +++ b/test/support/resources/comedian.ex @@ -41,6 +41,7 @@ defmodule AshPostgres.Test.Comedian do end defmodule AshPostgres.Test.Comedian.HasJokes do + @moduledoc false use Ash.Resource.Calculation @impl true From 6cf1b661390689a1c4aa90ae4b9bb4c964d61ad4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Jan 2025 13:16:22 -0500 Subject: [PATCH 0847/1215] improvement: use prettier SQL in `Ash.calculate` --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 40723180..68a4cf00 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1787,7 +1787,7 @@ defmodule AshPostgres.DataLayer do |> Map.new(fn {dynamic, index} -> {index, dynamic} end) query = - Ecto.Query.from(row in fragment("UNNEST(ARRAY[1])"), select: ^dynamics) + Ecto.Query.from(row in fragment("(VALUES(1))"), select: ^dynamics) |> Map.put(:__ash_bindings__, query.__ash_bindings__) repo = From bb9bd5b7325f71eaadeb3d426867c70f8c329ec2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Jan 2025 13:28:52 -0500 Subject: [PATCH 0848/1215] chore: fix dialyzer --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 68a4cf00..3ef52d78 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1297,7 +1297,7 @@ defmodule AshPostgres.DataLayer do case Ash.Resource.Info.primary_key(query.resource) do [] -> case Ash.Resource.Info.identities(query.resource) do - %{keys: keys} -> keys + [%{keys: keys} | _] -> keys _ -> [] end From 0f38bf2c331a2598300daa410efd0a626397713c Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Thu, 16 Jan 2025 17:16:19 +0100 Subject: [PATCH 0849/1215] feat: add repo callback to disable atomic actions and error expressions (#464) --- lib/data_layer.ex | 14 ++++++++++---- lib/repo.ex | 12 +++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3ef52d78..274fd495 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -644,8 +644,11 @@ defmodule AshPostgres.DataLayer do def can?(_, :transact), do: true def can?(_, :composite_primary_key), do: true - def can?(_resource, {:atomic, :update}), do: true - def can?(_resource, {:atomic, :upsert}), do: true + def can?(resource, {:atomic, :update}), + do: not AshPostgres.DataLayer.Info.repo(resource, :mutate).disable_atomic_actions?() + + def can?(resource, {:atomic, :upsert}), + do: not AshPostgres.DataLayer.Info.repo(resource, :mutate).disable_atomic_actions?() def can?(_, :upsert), do: true def can?(_, :changeset_filter), do: true @@ -709,10 +712,13 @@ defmodule AshPostgres.DataLayer do def can?(_, {:aggregate_relationship, _}), do: true def can?(_, :timeout), do: true - def can?(_, :expr_error), do: true + + def can?(resource, :expr_error), + do: not AshPostgres.DataLayer.Info.repo(resource, :mutate).disable_expr_error?() def can?(resource, {:filter_expr, %Ash.Query.Function.Error{}}) do - "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :read).installed_extensions() && + not AshPostgres.DataLayer.Info.repo(resource, :mutate).disable_expr_error?() && + "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :read).installed_extensions() && "ash-functions" in AshPostgres.DataLayer.Info.repo(resource, :mutate).installed_extensions() end diff --git a/lib/repo.ex b/lib/repo.ex index a40cfd16..a0bb292d 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -104,6 +104,12 @@ defmodule AshPostgres.Repo do @doc "Should the repo should be dropped by `mix ash_postgres.drop`?" @callback drop?() :: boolean + @doc "Disable atomic actions for this repo" + @callback disable_atomic_actions?() :: boolean + + @doc "Disable expression errors for this repo" + @callback disable_expr_error?() :: boolean + defmacro __using__(opts) do quote bind_quoted: [opts: opts] do if Keyword.get(opts, :define_ecto_repo?, true) do @@ -133,6 +139,8 @@ defmodule AshPostgres.Repo do def override_migration_type(type), do: type def create?, do: true def drop?, do: true + def disable_atomic_actions?, do: false + def disable_expr_error?, do: false # default to false in 4.0 def prefer_transaction?, do: true @@ -288,7 +296,9 @@ defmodule AshPostgres.Repo do default_prefix: 0, override_migration_type: 1, create?: 0, - drop?: 0 + drop?: 0, + disable_atomic_actions?: 0, + disable_expr_error?: 0 # We do this switch because `!@warn_on_missing_ash_functions` in the function body triggers # a dialyzer error From 785fc608a8251fdbc3f6ba4f972fe44e734346c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:50:39 -0500 Subject: [PATCH 0850/1215] chore(deps): bump ash_sql in the production-dependencies group (#463) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.43 to 0.2.45 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.43...v0.2.45) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2a858f30..4caaedd6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.55", "81132171412dc92bd1630500e1403a48f5ea7948552c528189d13d4c4c181878", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6fb89c9bb6e158b2dab08184f3b6ebfd88785348c21b2a9fd5bd71b1f96e2c5e"}, - "ash_sql": {:hex, :ash_sql, "0.2.43", "80579d708630d3d31e9bb74b74d16ff8ef70de8c1d5043d87a682b5b17cb3154", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c3fc710129178c017002bd108fc9411b10ca1d276fa5e99bd01b99addf28ff42"}, + "ash_sql": {:hex, :ash_sql, "0.2.45", "9c73522c13db7c7a82c1d1599e3eeaa63a4272ad2c9f7649abb34de6404763ea", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "4a55465e49c2f0ba28f19a4b0dafeb2b031df8ab9fecf9607611d20918b34ca9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From 22915b666581ad623ac8f6086378610c0198b521 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 20 Jan 2025 11:51:42 -0500 Subject: [PATCH 0851/1215] fix: generate a repo when selecting one --- lib/igniter.ex | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/igniter.ex b/lib/igniter.ex index 2e00bb7b..02ef4476 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -101,12 +101,13 @@ if Code.ensure_loaded?(Igniter) do """) end - Igniter.Project.Module.create_module( - igniter, - repo, - default_repo_contents(otp_app, opts), - opts - ) + igniter = + Igniter.Project.Module.create_module( + igniter, + repo, + default_repo_contents(otp_app, opts), + opts + ) {igniter, repo} else From e2df7b600abeac95546fdcc1357712294684d74c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 20 Jan 2025 12:00:54 -0500 Subject: [PATCH 0852/1215] chore: release version v2.5.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93357fd0..a60eea78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.0](https://github.com/ash-project/ash_postgres/compare/v2.4.22...v2.5.0) (2025-01-20) + + + + +### Features: + +* add repo callback to disable atomic actions and error expressions (#464) + +### Bug Fixes: + +* generate a repo when selecting one + +* handle regex match correctly (#460) + +### Improvements: + +* use prettier SQL in `Ash.calculate` + +* add `c:AshPostgres.Repo.default_constraint_match_type` + +* mark ash_raise_error as STABLE + ## [v2.4.22](https://github.com/ash-project/ash_postgres/compare/v2.4.21...v2.4.22) (2025-01-13) diff --git a/mix.exs b/mix.exs index 06279f90..d6810d80 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.4.22" + @version "2.5.0" def project do [ From c9a911e582a4a6ac6ef712522372f8cb25b2f0dc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 Jan 2025 15:41:24 -0500 Subject: [PATCH 0853/1215] fix: handle cross global to tenant references in migration generator test: add test for ash_sql fix chore: remove unnecessary prod deps --- .../migration_generator.ex | 1 + lib/migration_generator/operation.ex | 25 +++++ mix.exs | 4 +- mix.lock | 13 ++- .../20250122190558.json | 106 ++++++++++++++++++ .../20250122203454.json | 77 +++++++++++++ .../20250122190558_migrate_resources46.exs | 48 ++++++++ .../20250122203454_migrate_resources4.exs | 45 ++++++++ test/multitenancy_test.exs | 16 +++ test/support/multitenancy/domain.ex | 2 + .../resources/cross_tenant_post_link.ex | 31 +++++ .../resources/non_multitenant_post_link.ex | 43 +++++++ test/support/multitenancy/resources/post.ex | 9 ++ test/support/resources/post.ex | 7 ++ 14 files changed, 421 insertions(+), 6 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json create mode 100644 priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json create mode 100644 priv/test_repo/migrations/20250122190558_migrate_resources46.exs create mode 100644 priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs create mode 100644 test/support/multitenancy/resources/cross_tenant_post_link.ex create mode 100644 test/support/multitenancy/resources/non_multitenant_post_link.ex diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2810ceb8..ca733282 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -815,6 +815,7 @@ defmodule AshPostgres.MigrationGenerator do %{ keys: pkey_names, name: name, + index_name: name, base_filter: nil, all_tenants?: false, nils_distinct?: false, diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 930a375b..a2a4a1af 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -247,6 +247,31 @@ defmodule AshPostgres.MigrationGenerator.Operation do |> join() end + def up(%{ + multitenancy: %{strategy: nil}, + attribute: + %{ + references: %{ + multitenancy: %{strategy: :context} + } + } = attribute + }) do + size = + if attribute.size do + "size: #{attribute.size}" + end + + [ + "add #{inspect(attribute.source)}", + inspect(attribute.type), + maybe_add_default(attribute.default), + maybe_add_primary_key(attribute.primary_key?), + size, + maybe_add_null(attribute.allow_nil?) + ] + |> join() + end + def up(%{ multitenancy: %{strategy: :context}, attribute: diff --git a/mix.exs b/mix.exs index d6810d80..1589d2b6 100644 --- a/mix.exs +++ b/mix.exs @@ -167,13 +167,11 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.4 and >= 3.4.48")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.43")}, - {:igniter, "~> 0.4 and >= 0.4.4", optional: true}, + {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:inflex, "~> 2.1"}, - {:owl, "~> 0.11"}, # dev/test dependencies {:ecto_dev_logger, "~> 0.14", only: :test}, {:eflame, "~> 1.0", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 4caaedd6..19b23f25 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.55", "81132171412dc92bd1630500e1403a48f5ea7948552c528189d13d4c4c181878", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6fb89c9bb6e158b2dab08184f3b6ebfd88785348c21b2a9fd5bd71b1f96e2c5e"}, + "ash": {:hex, :ash, "3.4.56", "2591ef830200b9840b1915b6e2889518964d6852a44e58f06a0752f7b9e114a3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7d8091db87aa72b7a9e1082edea5b17308942b58c4f24c2d01499db6de05486"}, "ash_sql": {:hex, :ash_sql, "0.2.45", "9c73522c13db7c7a82c1d1599e3eeaa63a4272ad2c9f7649abb34de6404763ea", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "4a55465e49c2f0ba28f19a4b0dafeb2b031df8ab9fecf9607611d20918b34ca9"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -18,10 +18,12 @@ "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "d571628fd829a510d219bcb7162400baff50977f", []}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "igniter": {:hex, :igniter, "0.5.8", "d91e90fecb99beadfa9d0d8434fbd4f0fe06ea1a1d29cae4dfd0cb058cb3a5c7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fef198324925405ea5c3b16166002be03b2d7497c038cfc9708aa557d27ba5a2"}, + "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "igniter": {:hex, :igniter, "0.5.16", "3a6cc46f2ac93c8772038d513df5da6ab37aa06538ab96feb9d84b631fbbc073", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b6b5c74ac01541e0cdb296a93f7cc76a783d2020c152c9bc1f314ba0e83e1421"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -29,16 +31,21 @@ "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, "reactor": {:hex, :reactor, "0.10.3", "41a8c34251148e36dd7c75aa8433f2c2f283f29c097f9eb84a630ab28dd75651", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b34380e22b69a35943a7bcceffd5a8b766870f1fc9052162a7ff74ef9cdb3b2"}, + "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.36", "07c921e5efb27f184267c3431d2f82099e24cac90748a47383dd75cbfb558268", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e5ac56b75e5ad43da6d8302b6713277488f8e9a3abdba9aae8f0d0f9cff04538"}, + "spark": {:hex, :spark, "2.2.38", "cb84e7229122e1f1e0f8584ec65268ef6cd9a14dcc85a37eaaf70470267044ef", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "cca1322243506dee820fe72e6d323e76ffb9eb1fe44f0591355c9be5eccb0431"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json b/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json new file mode 100644 index 00000000..e0577141 --- /dev/null +++ b/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json @@ -0,0 +1,106 @@ +{ + "attributes": [ + { + "allow_nil?": true, + "default": "\"active\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "state", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "name": "non_multitenant_post_links_source_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_posts" + }, + "size": null, + "source": "source_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "non_multitenant_post_links_dest_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "dest_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "5B5A0B9459B9D31BE1BEA278DC64440D848D117EA9D43AB92C23C85ADB65102D", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "non_multitenant_post_links_unique_link_index", + "keys": [ + { + "type": "atom", + "value": "source_id" + }, + { + "type": "atom", + "value": "dest_id" + } + ], + "name": "unique_link", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "non_multitenant_post_links" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json b/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json new file mode 100644 index 00000000..b1f8c6bd --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json @@ -0,0 +1,77 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "cross_tenant_post_links_source_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "source_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "name": "cross_tenant_post_links_dest_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_posts" + }, + "size": null, + "source": "dest_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "A58B6473A5BEDECB3AFCC907EB96BD983DD78019B54016D7DE818C46B36303A0", + "identities": [], + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "cross_tenant_post_links" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250122190558_migrate_resources46.exs b/priv/test_repo/migrations/20250122190558_migrate_resources46.exs new file mode 100644 index 00000000..9f7eec04 --- /dev/null +++ b/priv/test_repo/migrations/20250122190558_migrate_resources46.exs @@ -0,0 +1,48 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources46 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 + create table(:non_multitenant_post_links, primary_key: false) do + add(:state, :text, default: "active") + add(:source_id, :uuid, primary_key: true, null: false) + + add( + :dest_id, + references(:posts, + column: :id, + name: "non_multitenant_post_links_dest_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + end + + create( + unique_index(:non_multitenant_post_links, [:source_id, :dest_id], + name: "non_multitenant_post_links_unique_link_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:non_multitenant_post_links, [:source_id, :dest_id], + name: "non_multitenant_post_links_unique_link_index" + ) + ) + + drop(constraint(:non_multitenant_post_links, "non_multitenant_post_links_source_id_fkey")) + + drop(constraint(:non_multitenant_post_links, "non_multitenant_post_links_dest_id_fkey")) + + drop(table(:non_multitenant_post_links)) + end +end diff --git a/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs b/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs new file mode 100644 index 00000000..36cbc1d1 --- /dev/null +++ b/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs @@ -0,0 +1,45 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources4 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 + create table(:cross_tenant_post_links, primary_key: false, prefix: prefix()) do + add( + :source_id, + references(:posts, + column: :id, + name: "cross_tenant_post_links_source_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + + add( + :dest_id, + references(:multitenant_posts, + column: :id, + name: "cross_tenant_post_links_dest_id_fkey", + type: :uuid, + prefix: prefix() + ), + primary_key: true, + null: false + ) + end + end + + def down do + drop(constraint(:cross_tenant_post_links, "cross_tenant_post_links_source_id_fkey")) + + drop(constraint(:cross_tenant_post_links, "cross_tenant_post_links_dest_id_fkey")) + + drop(table(:cross_tenant_post_links, prefix: prefix())) + end +end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 0c3326b9..afd5cbe5 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -1,7 +1,9 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false + require Ash.Query alias AshPostgres.MultitenancyTest.{Org, Post, User} + alias AshPostgres.Test.Post, as: GlobalPost setup do org1 = @@ -44,6 +46,20 @@ defmodule AshPostgres.Test.MultitenancyTest do |> Ash.read!() end + test "joining to non multitenant through relationship works", %{org1: org1} do + Post + |> Ash.Query.filter(linked_non_multitenant_posts.title == "fred") + |> Ash.Query.set_tenant("org_" <> org1.id) + |> Ash.read!() + end + + test "joining from non multitenant through relationship works", %{org1: org1} do + GlobalPost + |> Ash.Query.filter(linked_multitenant_posts.name == "fred") + |> Ash.Query.set_tenant("org_" <> org1.id) + |> Ash.read!() + end + test "attribute multitenancy works with authorization", %{org1: org1} do user = User diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 68a5d9de..2394c234 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -7,6 +7,8 @@ defmodule AshPostgres.MultitenancyTest.Domain do resource(AshPostgres.MultitenancyTest.User) resource(AshPostgres.MultitenancyTest.Post) resource(AshPostgres.MultitenancyTest.PostLink) + resource(AshPostgres.MultitenancyTest.NonMultitenantPostLink) + resource(AshPostgres.MultitenancyTest.CrossTenantPostLink) end authorization do diff --git a/test/support/multitenancy/resources/cross_tenant_post_link.ex b/test/support/multitenancy/resources/cross_tenant_post_link.ex new file mode 100644 index 00000000..0bcc7111 --- /dev/null +++ b/test/support/multitenancy/resources/cross_tenant_post_link.ex @@ -0,0 +1,31 @@ +defmodule AshPostgres.MultitenancyTest.CrossTenantPostLink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "cross_tenant_post_links" + repo AshPostgres.TestRepo + end + + multitenancy do + strategy(:context) + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + relationships do + belongs_to(:source, AshPostgres.Test.Post, + primary_key?: true, + allow_nil?: false + ) + + belongs_to(:dest, AshPostgres.MultitenancyTest.Post, + primary_key?: true, + allow_nil?: false + ) + end +end diff --git a/test/support/multitenancy/resources/non_multitenant_post_link.ex b/test/support/multitenancy/resources/non_multitenant_post_link.ex new file mode 100644 index 00000000..9d986bdb --- /dev/null +++ b/test/support/multitenancy/resources/non_multitenant_post_link.ex @@ -0,0 +1,43 @@ +defmodule AshPostgres.MultitenancyTest.NonMultitenantPostLink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "non_multitenant_post_links" + repo AshPostgres.TestRepo + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + identities do + identity(:unique_link, [:source_id, :dest_id]) + end + + attributes do + attribute :state, :atom do + public?(true) + constraints(one_of: [:active, :archived]) + default(:active) + end + end + + relationships do + belongs_to :source, AshPostgres.MultitenancyTest.Post do + public?(true) + allow_nil?(false) + primary_key?(true) + end + + belongs_to :dest, AshPostgres.Test.Post do + public?(true) + allow_nil?(false) + primary_key?(true) + end + end +end diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index d7999771..e097fffb 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -56,6 +56,15 @@ defmodule AshPostgres.MultitenancyTest.Post do source_attribute_on_join_resource(:source_id) destination_attribute_on_join_resource(:dest_id) end + + # has_many(:non_multitenant_post_links, AshPostgres.MultitenancyTest.NonMultitenantPostLink) + + many_to_many :linked_non_multitenant_posts, AshPostgres.Test.Post do + through(AshPostgres.MultitenancyTest.NonMultitenantPostLink) + join_relationship(:non_multitenant_post_links) + source_attribute_on_join_resource(:source_id) + destination_attribute_on_join_resource(:dest_id) + end end calculations do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d9dfcdf9..6a3a1904 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -530,6 +530,13 @@ defmodule AshPostgres.Test.Post do destination_attribute_on_join_resource: :destination_post_id ) + many_to_many(:linked_multitenant_posts, AshPostgres.MultitenancyTest.Post, + public?: true, + through: AshPostgres.MultitenancyTest.CrossTenantPostLink, + source_attribute_on_join_resource: :source_id, + destination_attribute_on_join_resource: :dest_id + ) + many_to_many(:followers, AshPostgres.Test.User, public?: true, through: AshPostgres.Test.PostFollower, From 8cc8d632154559605a03a6c6a61652983cec750d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 Jan 2025 15:44:02 -0500 Subject: [PATCH 0854/1215] WIP --- .../dsls/DSL-AshPostgres.DataLayer.md | 20 +++++++++---------- mix.lock | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/documentation/dsls/DSL-AshPostgres.DataLayer.md b/documentation/dsls/DSL-AshPostgres.DataLayer.md index 4ad2f0b3..a3938dcf 100644 --- a/documentation/dsls/DSL-AshPostgres.DataLayer.md +++ b/documentation/dsls/DSL-AshPostgres.DataLayer.md @@ -1,7 +1,7 @@ -# DSL: AshPostgres.DataLayer +# AshPostgres.DataLayer A postgres data layer that leverages Ecto's postgres capabilities. @@ -58,7 +58,7 @@ end | [`polymorphic?`](#postgres-polymorphic?){: #postgres-polymorphic? } | `boolean` | `false` | Declares this resource as polymorphic. See the [polymorphic resources guide](/documentation/topics/resources/polymorphic-resources.md) for more. | -## postgres.custom_indexes +### postgres.custom_indexes A section for configuring indexes to be created by the migration generator. In general, prefer to use `identities` for simple unique constraints. This is a tool to allow @@ -80,7 +80,7 @@ end -## postgres.custom_indexes.index +### postgres.custom_indexes.index ```elixir index fields ``` @@ -128,7 +128,7 @@ index ["column", "column2"], unique: true, where: "thing = TRUE" Target: `AshPostgres.CustomIndex` -## postgres.custom_statements +### postgres.custom_statements A section for configuring custom statements to be added to migrations. Changing custom statements may require manual intervention, because Ash can't determine what order they should run @@ -161,7 +161,7 @@ end -## postgres.custom_statements.statement +### postgres.custom_statements.statement ```elixir statement name ``` @@ -205,7 +205,7 @@ end Target: `AshPostgres.Statement` -## postgres.manage_tenant +### postgres.manage_tenant Configuration for the behavior of a resource that manages a tenant @@ -235,7 +235,7 @@ end -## postgres.references +### postgres.references A section for configuring the references (foreign keys) in resource migrations. This section is only relevant if you are using the migration generator with this resource. @@ -266,7 +266,7 @@ end -## postgres.references.reference +### postgres.references.reference ```elixir reference relationship ``` @@ -317,7 +317,7 @@ reference :post, on_delete: :delete, on_update: :update, name: "comments_to_post Target: `AshPostgres.Reference` -## postgres.check_constraints +### postgres.check_constraints A section for configuring the check constraints for a given table. This can be used to automatically create those check constraints, or just to provide message when they are raised @@ -338,7 +338,7 @@ end -## postgres.check_constraints.check_constraint +### postgres.check_constraints.check_constraint ```elixir check_constraint attribute, name ``` diff --git a/mix.lock b/mix.lock index 19b23f25..bd880a5e 100644 --- a/mix.lock +++ b/mix.lock @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.38", "cb84e7229122e1f1e0f8584ec65268ef6cd9a14dcc85a37eaaf70470267044ef", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "cca1322243506dee820fe72e6d323e76ffb9eb1fe44f0591355c9be5eccb0431"}, + "spark": {:hex, :spark, "2.2.39", "6c9e5d89146df8f7729236d9700518f53e1d6a7d6aaf9cc5ae3c7e4ab11f65ee", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "115f35d263895ebd810faf197ba3e42d5a0912c51e5350af899142d33aaa11dd"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From abc9ad1d494959a60b4187e50537db437139353f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 22 Jan 2025 22:44:56 -0500 Subject: [PATCH 0855/1215] chore: only update lock file compatible deps --- .github/dependabot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2f0ee6ff..a3168e45 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,6 +2,7 @@ version: 2 updates: - package-ecosystem: mix directory: "/" + versioning-strategy: lockfile-only schedule: interval: weekly day: thursday From d6afc183f2487dbd35950886104f208d9c98a2a6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 Jan 2025 00:38:01 -0500 Subject: [PATCH 0856/1215] chore: update deps --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index bd880a5e..968886aa 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.56", "2591ef830200b9840b1915b6e2889518964d6852a44e58f06a0752f7b9e114a3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7d8091db87aa72b7a9e1082edea5b17308942b58c4f24c2d01499db6de05486"}, - "ash_sql": {:hex, :ash_sql, "0.2.45", "9c73522c13db7c7a82c1d1599e3eeaa63a4272ad2c9f7649abb34de6404763ea", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "4a55465e49c2f0ba28f19a4b0dafeb2b031df8ab9fecf9607611d20918b34ca9"}, + "ash_sql": {:hex, :ash_sql, "0.2.47", "812194f28ffe88cb6ff30c32615e08fe49c4157dbc2cc80bf11f4844192c1f24", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1c3563c10d29b7e36498ee58fe652882ce4931d6a736025c6ccf7e862ddda191"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.39", "6c9e5d89146df8f7729236d9700518f53e1d6a7d6aaf9cc5ae3c7e4ab11f65ee", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "115f35d263895ebd810faf197ba3e42d5a0912c51e5350af899142d33aaa11dd"}, + "spark": {:hex, :spark, "2.2.40", "4fef851c346d891ce2aaf13d72e8af9b4371bf3059530b58c4a1149923faaf37", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d3cd4487932f4c63261c67568cec77d535dc6f9546c334d28f3b9d17c7f23fc0"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 846a98ac9052d07666f922516dd25acec4d52ad4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:42:38 -0500 Subject: [PATCH 0857/1215] chore(deps): bump ash_sql in the production-dependencies group (#465) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.45 to 0.2.47 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.45...v0.2.47) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 10f4354df2319b876887e4db2712c6eafc99d01a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 Jan 2025 11:52:41 -0500 Subject: [PATCH 0858/1215] chore: add tests for nested many to many issue --- .../comment_links/20250123161002.json | 96 +++++++++++ .../test_repo/content/20250123164209.json | 78 +++++++++ .../20250123164209.json | 77 +++++++++ .../test_repo/note/20250123164209.json | 59 +++++++ .../test_repo/staff_group/20250123164209.json | 49 ++++++ .../staff_group_member/20250123164209.json | 87 ++++++++++ .../20250123161002_migrate_resources47.exs | 57 +++++++ .../20250123164209_migrate_resources48.exs | 160 ++++++++++++++++++ test/complex_calculations_test.exs | 6 + test/support/domain.ex | 6 + test/support/resources/comment.ex | 11 ++ test/support/resources/comment_link.ex | 35 ++++ test/support/resources/content.ex | 36 ++++ .../resources/content_visibility_group.ex | 21 +++ test/support/resources/note.ex | 36 ++++ test/support/resources/post.ex | 5 + test/support/resources/staff_group.ex | 32 ++++ test/support/resources/staff_group_member.ex | 25 +++ test/test_helper.exs | 2 +- 19 files changed, 877 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/comment_links/20250123161002.json create mode 100644 priv/resource_snapshots/test_repo/content/20250123164209.json create mode 100644 priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json create mode 100644 priv/resource_snapshots/test_repo/note/20250123164209.json create mode 100644 priv/resource_snapshots/test_repo/staff_group/20250123164209.json create mode 100644 priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json create mode 100644 priv/test_repo/migrations/20250123161002_migrate_resources47.exs create mode 100644 priv/test_repo/migrations/20250123164209_migrate_resources48.exs create mode 100644 test/support/resources/comment_link.ex create mode 100644 test/support/resources/content.ex create mode 100644 test/support/resources/content_visibility_group.ex create mode 100644 test/support/resources/note.ex create mode 100644 test/support/resources/staff_group.ex create mode 100644 test/support/resources/staff_group_member.ex diff --git a/priv/resource_snapshots/test_repo/comment_links/20250123161002.json b/priv/resource_snapshots/test_repo/comment_links/20250123161002.json new file mode 100644 index 00000000..7f029e7d --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_links/20250123161002.json @@ -0,0 +1,96 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "comment_links_source_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "comments" + }, + "size": null, + "source": "source_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "comment_links_dest_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "comments" + }, + "size": null, + "source": "dest_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "C6B8BE5033D412C2E597F975082E6D985EC260BED163E504500FFB684FAD0BD2", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "comment_links_unique_link_index", + "keys": [ + { + "type": "atom", + "value": "source_id" + }, + { + "type": "atom", + "value": "dest_id" + } + ], + "name": "unique_link", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "comment_links" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/content/20250123164209.json b/priv/resource_snapshots/test_repo/content/20250123164209.json new file mode 100644 index 00000000..58a9de23 --- /dev/null +++ b/priv/resource_snapshots/test_repo/content/20250123164209.json @@ -0,0 +1,78 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "content_note_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "note" + }, + "size": null, + "source": "note_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "F045A8746F9BEC0CA9C585954072792A9DEB0FE0702789163E2F6BF9CC1A41A1", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "content" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json b/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json new file mode 100644 index 00000000..f49c0ae5 --- /dev/null +++ b/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json @@ -0,0 +1,77 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "content_visibility_group_content_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "content" + }, + "size": null, + "source": "content_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "content_visibility_group_staff_group_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "staff_group" + }, + "size": null, + "source": "staff_group_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "36C3B7305D83A64EAB2A7B934D2961D7ACA0557254D2DF059EF500BADFF4A177", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "content_visibility_group" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/note/20250123164209.json b/priv/resource_snapshots/test_repo/note/20250123164209.json new file mode 100644 index 00000000..469e89f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/note/20250123164209.json @@ -0,0 +1,59 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "body", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "3AEFA76A190732E6BDB6EA0686B4D6872F1E6395CFD58975853FDA3C3CADD98C", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "note" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/staff_group/20250123164209.json b/priv/resource_snapshots/test_repo/staff_group/20250123164209.json new file mode 100644 index 00000000..530dee1b --- /dev/null +++ b/priv/resource_snapshots/test_repo/staff_group/20250123164209.json @@ -0,0 +1,49 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "4353CC893BCF75218813E38289037CD3F213C4CFC4200426351DDCD5BA48F778", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "staff_group" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json b/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json new file mode 100644 index 00000000..9ef0e0aa --- /dev/null +++ b/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json @@ -0,0 +1,87 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "staff_group_member_staff_group_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "staff_group" + }, + "size": null, + "source": "staff_group_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "staff_group_member_user_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "users" + }, + "size": null, + "source": "user_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "6B740A55EA43FF7876BDA82F624D11ADB1539EF574541A508399280D0B0DA083", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "staff_group_member" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250123161002_migrate_resources47.exs b/priv/test_repo/migrations/20250123161002_migrate_resources47.exs new file mode 100644 index 00000000..59036b61 --- /dev/null +++ b/priv/test_repo/migrations/20250123161002_migrate_resources47.exs @@ -0,0 +1,57 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources47 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 + create table(:comment_links, primary_key: false) do + add( + :source_id, + references(:comments, + column: :id, + name: "comment_links_source_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + + add( + :dest_id, + references(:comments, + column: :id, + name: "comment_links_dest_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + end + + create( + unique_index(:comment_links, [:source_id, :dest_id], + name: "comment_links_unique_link_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:comment_links, [:source_id, :dest_id], + name: "comment_links_unique_link_index" + ) + ) + + drop(constraint(:comment_links, "comment_links_source_id_fkey")) + + drop(constraint(:comment_links, "comment_links_dest_id_fkey")) + + drop(table(:comment_links)) + end +end diff --git a/priv/test_repo/migrations/20250123164209_migrate_resources48.exs b/priv/test_repo/migrations/20250123164209_migrate_resources48.exs new file mode 100644 index 00000000..99bc4982 --- /dev/null +++ b/priv/test_repo/migrations/20250123164209_migrate_resources48.exs @@ -0,0 +1,160 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources48 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 + create table(:staff_group, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + + create table(:content, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:note_id, :uuid) + end + + create table(:note, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:content) do + modify( + :note_id, + references(:note, + column: :id, + name: "content_note_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:note) do + add(:body, :text, null: false) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + + create table(:staff_group_member, primary_key: false) do + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add( + :staff_group_id, + references(:staff_group, + column: :id, + name: "staff_group_member_staff_group_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + + add( + :user_id, + references(:users, + column: :id, + name: "staff_group_member_user_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + end + + create table(:content_visibility_group, primary_key: false) do + add( + :content_id, + references(:content, + column: :id, + name: "content_visibility_group_content_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + + add( + :staff_group_id, + references(:staff_group, + column: :id, + name: "content_visibility_group_staff_group_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + end + end + + def down do + drop(constraint(:content_visibility_group, "content_visibility_group_content_id_fkey")) + + drop(constraint(:content_visibility_group, "content_visibility_group_staff_group_id_fkey")) + + drop(table(:content_visibility_group)) + + drop(constraint(:staff_group_member, "staff_group_member_staff_group_id_fkey")) + + drop(constraint(:staff_group_member, "staff_group_member_user_id_fkey")) + + drop(table(:staff_group_member)) + + alter table(:note) do + remove(:updated_at) + remove(:inserted_at) + remove(:body) + end + + drop(constraint(:content, "content_note_id_fkey")) + + alter table(:content) do + modify(:note_id, :uuid) + end + + drop(table(:note)) + + drop(table(:content)) + + drop(table(:staff_group)) + end +end diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 383a0ac9..73fd689d 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -340,4 +340,10 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do |> Ash.Query.filter(sum_of_author_count_of_posts == 1) |> Ash.read!() end + + test "filters with nested related list aggregate references don't raise errors" do + AshPostgres.Test.Note + |> Ash.Query.for_read(:failing_many_reference) + |> Ash.read!(page: [count: true]) + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index f690211a..c2d867a7 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -7,6 +7,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Post) resource(AshPostgres.Test.Comedian) resource(AshPostgres.Test.Comment) + resource(AshPostgres.Test.CommentLink) resource(AshPostgres.Test.IntegerPost) resource(AshPostgres.Test.Rating) resource(AshPostgres.Test.PostLink) @@ -16,10 +17,15 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.User) resource(AshPostgres.Test.Invite) resource(AshPostgres.Test.Joke) + resource(AshPostgres.Test.Note) + resource(AshPostgres.Test.StaffGroup) + resource(AshPostgres.Test.StaffGroupMember) + resource(AshPostgres.Test.Content) resource(AshPostgres.Test.Account) resource(AshPostgres.Test.Organization) resource(AshPostgres.Test.Manager) resource(AshPostgres.Test.Entity) + resource(AshPostgres.Test.ContentVisibilityGroup) resource(AshPostgres.Test.TempEntity) resource(AshPostgres.Test.Permalink) resource(AshPostgres.Test.Record) diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index 82bccc9a..fe4a818e 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -48,6 +48,10 @@ defmodule AshPostgres.Test.Comment do count(:co_popular_comments, [:post, :popular_comments]) count(:count_of_comments_containing_title, [:post, :comments_containing_title]) list(:posts_for_comments_containing_title, [:post, :comments_containing_title, :post], :title) + + list :linked_comment_post_ids, [:linked_comments, :post], :id do + uniq?(true) + end end calculations do @@ -72,6 +76,13 @@ defmodule AshPostgres.Test.Comment do public?(true) end + many_to_many(:linked_comments, AshPostgres.Test.Comment) do + public?(true) + through(AshPostgres.Test.CommentLink) + source_attribute_on_join_resource(:source_id) + destination_attribute_on_join_resource(:dest_id) + end + has_many(:ratings, AshPostgres.Test.Rating, public?: true, destination_attribute: :resource_id, diff --git a/test/support/resources/comment_link.ex b/test/support/resources/comment_link.ex new file mode 100644 index 00000000..2654ff0f --- /dev/null +++ b/test/support/resources/comment_link.ex @@ -0,0 +1,35 @@ +defmodule AshPostgres.Test.CommentLink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "comment_links" + repo AshPostgres.TestRepo + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + identities do + identity(:unique_link, [:source_id, :dest_id]) + end + + relationships do + belongs_to :source, AshPostgres.Test.Comment do + public?(true) + allow_nil?(false) + primary_key?(true) + end + + belongs_to :dest, AshPostgres.Test.Comment do + public?(true) + allow_nil?(false) + primary_key?(true) + end + end +end diff --git a/test/support/resources/content.ex b/test/support/resources/content.ex new file mode 100644 index 00000000..370d7016 --- /dev/null +++ b/test/support/resources/content.ex @@ -0,0 +1,36 @@ +defmodule AshPostgres.Test.Content do + @moduledoc false + use Ash.Resource, + otp_app: :ash_postgres, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "content" + repo AshPostgres.TestRepo + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + timestamps() + end + + relationships do + belongs_to(:note, AshPostgres.Test.Note) + + many_to_many :visibility_groups, AshPostgres.Test.StaffGroup do + through(AshPostgres.Test.ContentVisibilityGroup) + end + end + + aggregates do + list :visibility_group_staff_ids, [:visibility_groups, :members], :id do + uniq?(true) + end + end +end diff --git a/test/support/resources/content_visibility_group.ex b/test/support/resources/content_visibility_group.ex new file mode 100644 index 00000000..d618fcb4 --- /dev/null +++ b/test/support/resources/content_visibility_group.ex @@ -0,0 +1,21 @@ +defmodule AshPostgres.Test.ContentVisibilityGroup do + @moduledoc false + use Ash.Resource, + otp_app: :ash_postgres, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "content_visibility_group" + repo AshPostgres.TestRepo + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + relationships do + belongs_to(:content, AshPostgres.Test.Content, primary_key?: true, allow_nil?: false) + belongs_to(:staff_group, AshPostgres.Test.StaffGroup, primary_key?: true, allow_nil?: false) + end +end diff --git a/test/support/resources/note.ex b/test/support/resources/note.ex new file mode 100644 index 00000000..b5880dfd --- /dev/null +++ b/test/support/resources/note.ex @@ -0,0 +1,36 @@ +defmodule AshPostgres.Test.Note do + @moduledoc false + use Ash.Resource, + otp_app: :ash_postgres, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "note" + repo AshPostgres.TestRepo + end + + actions do + defaults([:read]) + + read :failing_many_reference do + pagination(keyset?: true, default_limit: 25) + filter(expr(count_nils(content.visibility_group_staff_ids) == 0)) + end + end + + attributes do + uuid_primary_key(:id) + + attribute :body, :string do + allow_nil?(false) + public?(true) + end + + timestamps() + end + + relationships do + has_one(:content, AshPostgres.Test.Content) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6a3a1904..d41f4198 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -345,6 +345,11 @@ defmodule AshPostgres.Test.Post do accept([:title]) change(optimistic_lock(:version)) end + + read :read_with_related_list_agg_filter do + pagination(keyset?: true, default_limit: 25) + filter(expr(count_nils(latest_comment.linked_comment_post_ids) == 0)) + end end identities do diff --git a/test/support/resources/staff_group.ex b/test/support/resources/staff_group.ex new file mode 100644 index 00000000..3902ca07 --- /dev/null +++ b/test/support/resources/staff_group.ex @@ -0,0 +1,32 @@ +defmodule AshPostgres.Test.StaffGroup do + @moduledoc false + use Ash.Resource, + otp_app: :ash_postgres, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "staff_group" + repo AshPostgres.TestRepo + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + attributes do + uuid_primary_key(:id) + + timestamps() + end + + relationships do + many_to_many :members, AshPostgres.Test.User do + through(AshPostgres.Test.StaffGroupMember) + end + end + + aggregates do + count(:members_count, :members) + end +end diff --git a/test/support/resources/staff_group_member.ex b/test/support/resources/staff_group_member.ex new file mode 100644 index 00000000..7624a9ce --- /dev/null +++ b/test/support/resources/staff_group_member.ex @@ -0,0 +1,25 @@ +defmodule AshPostgres.Test.StaffGroupMember do + @moduledoc false + use Ash.Resource, + otp_app: :ash_postgres, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "staff_group_member" + repo AshPostgres.TestRepo + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + attributes do + create_timestamp(:inserted_at) + end + + relationships do + belongs_to(:staff_group, AshPostgres.Test.StaffGroup, primary_key?: true, allow_nil?: false) + belongs_to(:user, AshPostgres.Test.User, primary_key?: true, allow_nil?: false) + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 531e4ba8..97a70f24 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.start(capture_log: true) +ExUnit.start() exclude_tags = case System.get_env("PG_VERSION") do From cef9d02b48a510185e959215a0d2e28af81ad635 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 Jan 2025 13:28:05 -0500 Subject: [PATCH 0859/1215] chore: only build docs once --- .github/workflows/elixir.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 4590fca9..73c0e340 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -16,5 +16,6 @@ jobs: with: postgres: true postgres-version: ${{ matrix.postgres-version }} + publish-docs: ${{ matrix.postgres-version == "16" }} secrets: hex_api_key: ${{ secrets.HEX_API_KEY }} From 260823deabc14f246b14a8fccb260764d86d8dc8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 23 Jan 2025 13:29:59 -0500 Subject: [PATCH 0860/1215] ci: single quotes --- .github/workflows/elixir.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 73c0e340..3b2c94fd 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -16,6 +16,6 @@ jobs: with: postgres: true postgres-version: ${{ matrix.postgres-version }} - publish-docs: ${{ matrix.postgres-version == "16" }} + publish-docs: ${{ matrix.postgres-version == '16' }} secrets: hex_api_key: ${{ secrets.HEX_API_KEY }} From f628d37bc7b26c2471e53fd112e1964dc5d9b908 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 26 Jan 2025 00:15:05 -0500 Subject: [PATCH 0861/1215] ci: only run release on postgres 16 CI --- .github/workflows/elixir.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 3b2c94fd..084df209 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -17,5 +17,8 @@ jobs: postgres: true postgres-version: ${{ matrix.postgres-version }} publish-docs: ${{ matrix.postgres-version == '16' }} + release: ${{ matrix.postgres-version == '16' }} + igniter-upgrade: ${{matrix.postgres-version == '16'}} + secrets: hex_api_key: ${{ secrets.HEX_API_KEY }} From 837ea8f9ecf4ca1ad1c4a5d78bf9a86d5a054fed Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 27 Jan 2025 13:44:32 -0500 Subject: [PATCH 0862/1215] chore: release version v2.5.1 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a60eea78..5cc0c85a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.1](https://github.com/ash-project/ash_postgres/compare/v2.5.0...v2.5.1) (2025-01-27) + + + + +### Bug Fixes: + +* handle cross global to tenant references in migration generator + ## [v2.5.0](https://github.com/ash-project/ash_postgres/compare/v2.4.22...v2.5.0) (2025-01-20) diff --git a/mix.exs b/mix.exs index 1589d2b6..becd385c 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.0" + @version "2.5.1" def project do [ From dbadef83764dd6b7f3f02c03423ed3bce1b894ad Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 27 Jan 2025 20:53:49 -0500 Subject: [PATCH 0863/1215] docs: update ex_doc & spark for better search --- mix.exs | 15 ++++++--------- mix.lock | 10 +++++----- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/mix.exs b/mix.exs index becd385c..29a95129 100644 --- a/mix.exs +++ b/mix.exs @@ -32,7 +32,7 @@ defmodule AshPostgres.MixProject do dialyzer: [ plt_add_apps: [:ecto, :ash, :mix] ], - docs: docs(), + docs: &docs/0, aliases: aliases(), package: package(), source_url: "https://github.com/ash-project/ash_postgres/", @@ -98,7 +98,8 @@ defmodule AshPostgres.MixProject do "documentation/topics/advanced/expressions.md", "documentation/topics/advanced/schema-based-multitenancy.md", "documentation/topics/advanced/manual-relationships.md", - "documentation/dsls/DSL-AshPostgres.DataLayer.md", + {"documentation/dsls/DSL-AshPostgres.DataLayer.md", + search_data: Spark.Docs.search_data_for(AshPostgres.DataLayer)}, "CHANGELOG.md" ], groups_for_extras: [ @@ -107,8 +108,7 @@ defmodule AshPostgres.MixProject do Development: ~r"documentation/topics/development", "About AshPostgres": ["CHANGELOG.md"], Advanced: ~r"documentation/topics/advanced", - Reference: ~r"documentation/topics/dsls", - DSLs: ~r"documentation/dsls" + Reference: ~r"documentation/topics/dsls" ], skip_undefined_reference_warnings_on: [ "CHANGELOG.md", @@ -178,7 +178,7 @@ defmodule AshPostgres.MixProject do {:simple_sat, "~> 0.1", only: [:dev, :test]}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]}, - {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.37-rc", only: [:dev, :test], runtime: false}, {:ex_check, "~> 0.14", only: [:dev, :test]}, {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false}, @@ -233,14 +233,11 @@ defmodule AshPostgres.MixProject do docs: [ "spark.cheat_sheets", "docs", - "spark.replace_doc_links", - "spark.cheat_sheets_in_search" + "spark.replace_doc_links" ], format: "format --migrate", "spark.formatter": "spark.formatter --extensions AshPostgres.DataLayer", "spark.cheat_sheets": "spark.cheat_sheets --extensions AshPostgres.DataLayer", - "spark.cheat_sheets_in_search": - "spark.cheat_sheets_in_search --extensions AshPostgres.DataLayer", "test.generate_migrations": "ash_postgres.generate_migrations", "test.check_migrations": "ash_postgres.generate_migrations --check", "test.migrate_tenants": "ash_postgres.migrate --tenants", diff --git a/mix.lock b/mix.lock index 968886aa..0b1b8d83 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,7 @@ "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, @@ -16,14 +16,14 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "d571628fd829a510d219bcb7162400baff50977f", []}, + "ex_doc": {:hex, :ex_doc, "0.37.0-rc.0", "cf3b582b8410052a2ce92e181b4f92e6fe1e4d77e0c966715a112de189667cd6", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "32aa9e539f23aafd6bec490ec84d0bfc10ab9086561239aaf4aa8559f3ba559c"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.16", "3a6cc46f2ac93c8772038d513df5da6ab37aa06538ab96feb9d84b631fbbc073", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b6b5c74ac01541e0cdb296a93f7cc76a783d2020c152c9bc1f314ba0e83e1421"}, + "igniter": {:hex, :igniter, "0.5.20", "f95227c1cc1e9ee21151d670a3f1bce2151fb2cd7a81fec85a3b832f6ba0d866", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fbc48dfb15ce7cf35e2e688d969ebbaad7ca3291a21a297879169370cc8efef4"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -37,7 +37,7 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "owl": {:hex, :owl, "0.12.0", "0c4b48f90797a7f5f09ebd67ba7ebdc20761c3ec9c7928dfcafcb6d3c2d25c99", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "241d85ae62824dd72f9b2e4a5ba4e69ebb9960089a3c68ce6c1ddf2073db3c15"}, + "owl": {:hex, :owl, "0.12.1", "d3146087315c4528ee32411495ba10ec88102597b638d4d1455cf9d245dfb57a", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "d7eb9746aa89c40c46b479d6c2a70b82b94993520e40f21d0b09654f23eebf35"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, "reactor": {:hex, :reactor, "0.10.3", "41a8c34251148e36dd7c75aa8433f2c2f283f29c097f9eb84a630ab28dd75651", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b34380e22b69a35943a7bcceffd5a8b766870f1fc9052162a7ff74ef9cdb3b2"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.40", "4fef851c346d891ce2aaf13d72e8af9b4371bf3059530b58c4a1149923faaf37", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d3cd4487932f4c63261c67568cec77d535dc6f9546c334d28f3b9d17c7f23fc0"}, + "spark": {:hex, :spark, "2.2.42", "2e01e237329aa766bf367d0a4e17774ca0f5bdd147452889fee00b7b1d799330", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0b993234c7a145709e4ff97ee5afa0354ebc623c391d551f5555a4476d6336c1"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 0af8a48a76a88501e15d1727a9e9e6a26e9c718f Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Thu, 30 Jan 2025 01:01:17 +0100 Subject: [PATCH 0864/1215] test: add test for cascade destroy change (#466) --- test/cascade_destroy_test.exs | 25 +++++++++++++++++++++++++ test/support/resources/post.ex | 11 +++++++++++ 2 files changed, 36 insertions(+) create mode 100644 test/cascade_destroy_test.exs diff --git a/test/cascade_destroy_test.exs b/test/cascade_destroy_test.exs new file mode 100644 index 00000000..a407ea35 --- /dev/null +++ b/test/cascade_destroy_test.exs @@ -0,0 +1,25 @@ +defmodule AshPostgresTest.CascadeDestroyTest do + use AshPostgres.RepoCase, async: true + + alias AshPostgres.Test.{Post, Rating} + + test "can cascade destroy a has_many with parent filter" do + post = + Post.create!("post", %{score: 1}) + + Rating + |> Ash.Changeset.for_create(:create, %{score: 2, resource_id: post.id}) + |> Ash.Changeset.set_context(%{data_layer: %{table: "post_ratings"}}) + |> Ash.create!() + + post + |> Ash.Changeset.for_destroy(:cascade_destroy) + |> Ash.destroy!() + + assert [] = + Rating + |> Ash.Query.for_read(:read) + |> Ash.Query.set_context(%{data_layer: %{table: "post_ratings"}}) + |> Ash.read!() + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d41f4198..d221bdd6 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -230,6 +230,10 @@ defmodule AshPostgres.Test.Post do ) end + destroy :cascade_destroy do + change(cascade_destroy(:high_ratings, after_action?: false)) + end + update :update do primary?(true) require_atomic?(false) @@ -521,6 +525,13 @@ defmodule AshPostgres.Test.Post do relationship_context: %{data_layer: %{table: "post_ratings"}} ) + has_many :high_ratings, AshPostgres.Test.Rating do + public?(true) + destination_attribute(:resource_id) + relationship_context(%{data_layer: %{table: "post_ratings"}}) + filter(expr(score > parent(score))) + end + has_many(:post_links, AshPostgres.Test.PostLink, public?: true, destination_attribute: :source_post_id, From 96c8859283a07567946d0a7a09d618bf5e0dccba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:40:04 -0500 Subject: [PATCH 0865/1215] chore(deps): bump the production-dependencies group with 2 updates (#468) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash` from 3.4.56 to 3.4.60 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/3.4.56...v3.4.60) Updates `ash_sql` from 0.2.47 to 0.2.48 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.47...v0.2.48) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 0b1b8d83..0eb76542 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.56", "2591ef830200b9840b1915b6e2889518964d6852a44e58f06a0752f7b9e114a3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c7d8091db87aa72b7a9e1082edea5b17308942b58c4f24c2d01499db6de05486"}, - "ash_sql": {:hex, :ash_sql, "0.2.47", "812194f28ffe88cb6ff30c32615e08fe49c4157dbc2cc80bf11f4844192c1f24", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1c3563c10d29b7e36498ee58fe652882ce4931d6a736025c6ccf7e862ddda191"}, + "ash": {:hex, :ash, "3.4.60", "c5002ffde3ef108ca24faa44702573c6e64afe3dfe5a670a5253d252c93aecae", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b8e983f9cb973ed3300cd8fa261b136c0c389f8f51bee728cccc57edbe91a94"}, + "ash_sql": {:hex, :ash_sql, "0.2.48", "aea787441e97f9aaffe954e6f0f3b2d6206241d92bb104daf12b178befd65a1f", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b19c5d3bc89cc6575f87ebfa57db33d697831df54a812589b9f4638b8b94864b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -39,15 +39,15 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.1", "d3146087315c4528ee32411495ba10ec88102597b638d4d1455cf9d245dfb57a", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "d7eb9746aa89c40c46b479d6c2a70b82b94993520e40f21d0b09654f23eebf35"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, - "reactor": {:hex, :reactor, "0.10.3", "41a8c34251148e36dd7c75aa8433f2c2f283f29c097f9eb84a630ab28dd75651", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b34380e22b69a35943a7bcceffd5a8b766870f1fc9052162a7ff74ef9cdb3b2"}, + "reactor": {:hex, :reactor, "0.11.0", "a985b1b6d60562459a1e820c1cdf74f5408e62089be995ce805d1b30f6762f09", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "19b362159f15871715f6632b4bad6ef0c666b49968e53af73673edabadd0b88b"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.42", "2e01e237329aa766bf367d0a4e17774ca0f5bdd147452889fee00b7b1d799330", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0b993234c7a145709e4ff97ee5afa0354ebc623c391d551f5555a4476d6336c1"}, + "spark": {:hex, :spark, "2.2.43", "4ce84fcc9626f7759e9b9d698c95bdfcbbe9732184e1bc8f6ab9f9963bce2a6a", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "bbd83de69f4436e6f075d632d41110f840ce3a12c34d5499c15e60cf7b45a67b"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, - "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, + "splode": {:hex, :splode, "0.2.8", "289d4eec13e7a83061bc44827877eb4c575e1fdf198bd1a9c6449f9b64805059", [:mix], [], "hexpm", "dbe92fa526589416435e12203b56db1f74c834d207bc474016cedf930d987284"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, From f48fc0cad965e31f5d88b59e7b1b8ef5cbf7f7f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:40:21 -0500 Subject: [PATCH 0866/1215] chore(deps-dev): bump ex_doc in the dev-dependencies group (#469) Bumps the dev-dependencies group with 1 update: [ex_doc](https://github.com/elixir-lang/ex_doc). Updates `ex_doc` from 0.37.0-rc.0 to 0.37.0-rc.2 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.0-rc.0...v0.37.0-rc.2) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 0eb76542..5fbae7b6 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.37.0-rc.0", "cf3b582b8410052a2ce92e181b4f92e6fe1e4d77e0c966715a112de189667cd6", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "32aa9e539f23aafd6bec490ec84d0bfc10ab9086561239aaf4aa8559f3ba559c"}, + "ex_doc": {:hex, :ex_doc, "0.37.0-rc.2", "6e55e065aea63c2dfb3c0e18786a22d5107923ff7fb6a91f6e575f607735d09b", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "91e09b4ca47b2a83ce4c7035de7f0bb6e531c5d43cab19bb0c7820f73470df49"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, @@ -28,14 +28,14 @@ "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.1", "d3146087315c4528ee32411495ba10ec88102597b638d4d1455cf9d245dfb57a", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "d7eb9746aa89c40c46b479d6c2a70b82b94993520e40f21d0b09654f23eebf35"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, From 36dab3a180a5b57a0d6f4bddbc341bfe6605e1f5 Mon Sep 17 00:00:00 2001 From: Yusuke Higuchi <17329720+hy2k@users.noreply.github.com> Date: Thu, 30 Jan 2025 23:12:45 +0900 Subject: [PATCH 0867/1215] fix: update sql log switches for migration and rollback tasks (#470) --- lib/mix/tasks/ash_postgres.migrate.ex | 8 ++++++-- lib/mix/tasks/ash_postgres.rollback.ex | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index fd74ff65..1db3965d 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -18,7 +18,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do quiet: :boolean, prefix: :string, pool_size: :integer, - log_sql: :boolean, + log_migrations_sql: :boolean, + log_migrator_sql: :boolean, strict_version_order: :boolean, domains: :string, no_compile: :boolean, @@ -81,7 +82,10 @@ defmodule Mix.Tasks.AshPostgres.Migrate do * `--pool-size` - the pool size if the repository is started only for the task (defaults to 2) - * `--log-sql` - log the raw sql migrations are running + * `--log-migrations-sql` - log SQL generated by migration commands + + * `--log-migrator-sql` - log SQL generated by the migrator, such as + transactions, table locks, etc * `--strict-version-order` - abort when applying a migration with old timestamp diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index ac5da2fa..afdb3c8d 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -37,7 +37,8 @@ defmodule Mix.Tasks.AshPostgres.Rollback do * `--quiet` - do not log migration commands * `--prefix` - the prefix to run migrations on * `--pool-size` - the pool size if the repository is started only for the task (defaults to 1) - * `--log-sql` - log the raw sql migrations are running + * `--log-migrations-sql` - log SQL generated by migration commands + * `--log-migrator-sql` - log SQL generated by the migrator, such as transactions, table locks, etc * `--tenants` - roll back tenant migrations * `--only-tenants` - in combo with `--tenants`, only rolls back the provided tenants, e.g `tenant1,tenant2,tenant3` * `--except-tenants` - in combo with `--tenants`, does not rollback the provided tenants, e.g `tenant1,tenant2,tenant3` @@ -55,7 +56,8 @@ defmodule Mix.Tasks.AshPostgres.Rollback do quiet: :boolean, prefix: :string, pool_size: :integer, - log_sql: :boolean, + log_migrations_sql: :boolean, + log_migrator_sql: :boolean, only_tenants: :string, except_tenants: :string ], From ac3d0fa3e8dd8cce138684f0f219bd1e08d1b6cc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 30 Jan 2025 13:13:44 -0500 Subject: [PATCH 0868/1215] chore: make version parsing more consistent --- test/support/test_no_sandbox_repo.ex | 10 ++++++++-- test/test_helper.exs | 3 --- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index 201d3667..fca4bf13 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -9,8 +9,14 @@ defmodule AshPostgres.TestNoSandboxRepo do def min_pg_version do case System.get_env("PG_VERSION") do - nil -> %Version{major: 16, minor: 0, patch: 0} - version -> Version.parse!(version) + nil -> + %Version{major: 16, minor: 0, patch: 0} + + version -> + case Integer.parse(version) do + {major, ""} -> %Version{major: major, minor: 0, patch: 0} + _ -> Version.parse!(version) + end end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 97a70f24..8438eaf9 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -11,9 +11,6 @@ exclude_tags = "15" -> [:postgres_16] - "16" -> - [] - _ -> [] end From 6e29a4df55e05f88f04436a3320e789f3b510693 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 30 Jan 2025 15:12:03 -0500 Subject: [PATCH 0869/1215] test: test new start_of_day logic from ash core this test won't pass until ash is updated --- config/config.exs | 1 + mix.exs | 1 + mix.lock | 3 ++- test/calculation_test.exs | 5 +++++ test/support/resources/post.ex | 2 ++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 62650332..ee88e028 100644 --- a/config/config.exs +++ b/config/config.exs @@ -18,6 +18,7 @@ if Mix.env() == :dev do end if Mix.env() == :test do + config :elixir, :time_zone_database, Tz.TimeZoneDatabase config :ash_postgres, AshPostgres.TestRepo, log: false config :ash_postgres, AshPostgres.TestNoSandboxRepo, log: false diff --git a/mix.exs b/mix.exs index 29a95129..4ad5f2be 100644 --- a/mix.exs +++ b/mix.exs @@ -173,6 +173,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies + {:tz, "~> 0.28.1"}, {:ecto_dev_logger, "~> 0.14", only: :test}, {:eflame, "~> 1.0", only: [:dev, :test]}, {:simple_sat, "~> 0.1", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 5fbae7b6..6f2343ee 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.60", "c5002ffde3ef108ca24faa44702573c6e64afe3dfe5a670a5253d252c93aecae", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b8e983f9cb973ed3300cd8fa261b136c0c389f8f51bee728cccc57edbe91a94"}, - "ash_sql": {:hex, :ash_sql, "0.2.48", "aea787441e97f9aaffe954e6f0f3b2d6206241d92bb104daf12b178befd65a1f", [:mix], [{:ash, ">= 3.1.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "b19c5d3bc89cc6575f87ebfa57db33d697831df54a812589b9f4638b8b94864b"}, + "ash_sql": {:hex, :ash_sql, "0.2.49", "9c8a063d0ff9ee5b87716d4719751cee9224aae96ff59258621d3524c2aecdc6", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "681e8c98ea8f23cd406e9ed0ed09822fa7f08eaf9196d8acb092b9815ae0921d"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -52,6 +52,7 @@ "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, + "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, } diff --git a/test/calculation_test.exs b/test/calculation_test.exs index f1a091a2..3703ced7 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -44,6 +44,11 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + test "start_of_day functions the same as Elixir's start of ay" do + assert Ash.calculate!(Post, :start_of_day) == + Ash.Expr.eval!(Ash.Expr.expr(start_of_day(^DateTime.utc_now(), "EST"))) + end + @tag :regression test "an expression calculation that requires a left join & distinct doesn't raise errors on out of order params" do post = diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d221bdd6..d1082c54 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -750,6 +750,8 @@ defmodule AshPostgres.Test.Post do ) ) + calculate(:start_of_day, :datetime, expr(start_of_day(fragment("now()::timestamp"), "EST"))) + calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) calculate( From ad51da73c26927dca3f27361be786f4c48a25b82 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 31 Jan 2025 07:37:04 -0500 Subject: [PATCH 0870/1215] chore: fix build --- mix.lock | 2 +- test/support/resources/subquery/access.ex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 6f2343ee..e3e41139 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.60", "c5002ffde3ef108ca24faa44702573c6e64afe3dfe5a670a5253d252c93aecae", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b8e983f9cb973ed3300cd8fa261b136c0c389f8f51bee728cccc57edbe91a94"}, + "ash": {:hex, :ash, "3.4.61", "8c34174597ff8d5c0ff8da9a77ad207fb9bce8dfe680fcf1cbf23964d01be739", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e311aab8bf5a1a1bf63ac630a61d218a07de3da16d60f7972e7e85c0eb53d36"}, "ash_sql": {:hex, :ash_sql, "0.2.49", "9c8a063d0ff9ee5b87716d4719751cee9224aae96ff59258621d3524c2aecdc6", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "681e8c98ea8f23cd406e9ed0ed09822fa7f08eaf9196d8acb092b9815ae0921d"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/support/resources/subquery/access.ex b/test/support/resources/subquery/access.ex index a3df6531..90a356ff 100644 --- a/test/support/resources/subquery/access.ex +++ b/test/support/resources/subquery/access.ex @@ -5,6 +5,7 @@ defmodule AshPostgres.Test.Subquery.Access do use Ash.Resource, domain: AshPostgres.Test.Subquery.ParentDomain, data_layer: AshPostgres.DataLayer, + primary_read_warning?: false, authorizers: [ Ash.Policy.Authorizer ] From ce539a6dec74cedfe18855d1a80308540ccf0ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Fri, 31 Jan 2025 14:56:54 +0100 Subject: [PATCH 0871/1215] Improvement: generate migrations task support concurrent indexes flag (#471) --- .../migration_generator.ex | 10 ++++- lib/migration_generator/operation.ex | 10 ++++- .../tasks/ash_postgres.generate_migrations.ex | 4 +- test/migration_generator_test.exs | 39 +++++++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index ca733282..98a18c65 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -12,6 +12,7 @@ defmodule AshPostgres.MigrationGenerator do name: nil, tenant_migration_path: nil, quiet: false, + concurrent_indexes: false, current_snapshots: nil, answers: [], no_shell?: false, @@ -471,6 +472,9 @@ defmodule AshPostgres.MigrationGenerator do %Operation.AddCustomIndex{index: %{concurrently: true}} -> true + %Operation.AddUniqueIndex{concurrently: true} -> + true + _ -> false end) @@ -493,6 +497,9 @@ defmodule AshPostgres.MigrationGenerator do %Operation.AddCustomIndex{index: %{concurrently: true}} -> true + %Operation.AddUniqueIndex{concurrently: true} -> + true + _ -> false end) @@ -2000,7 +2007,8 @@ defmodule AshPostgres.MigrationGenerator do %Operation.AddUniqueIndex{ identity: identity, schema: snapshot.schema, - table: snapshot.table + table: snapshot.table, + concurrently: opts.concurrent_indexes } end) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index a2a4a1af..cc4f1975 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -807,7 +807,15 @@ defmodule AshPostgres.MigrationGenerator.Operation do defmodule AddUniqueIndex do @moduledoc false - defstruct [:identity, :table, :schema, :multitenancy, :old_multitenancy, no_phase: true] + defstruct [ + :identity, + :table, + :schema, + :multitenancy, + :old_multitenancy, + no_phase: true, + concurrently: false + ] import Helper diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index ea2c7383..4a126d24 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -22,6 +22,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do * `dry-run` - no files are created, instead the new migration is printed * `check` - no files are created, returns an exit(1) code if the current snapshots and resources don't fit * `snapshots-only` - no migrations are generated, only snapshots are stored + * `concurrent-indexes` - new identities will be run in a separate migration (like concurrent custom indexes) #### Snapshots @@ -96,7 +97,8 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do no_format: :boolean, dry_run: :boolean, check: :boolean, - dont_drop_columns: :boolean + dont_drop_columns: :boolean, + concurrent_indexes: :boolean ] ) diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index fd0e9045..131f8c4a 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -819,6 +819,45 @@ defmodule AshPostgres.MigrationGeneratorTest do "create unique_index(:posts, [:title], name: \"posts_unique_title_index\")" end + test "when concurrent-indexes flag set to true, identities are added in separate migration" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + attribute(:name, :string, public?: true) + end + + identities do + identity(:unique_title, [:title]) + identity(:unique_name, [:name]) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + concurrent_indexes: true, + format: false + ) + + assert [_file1, _file2, file3] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + file3_content = File.read!(file3) + + assert file3_content =~ ~S[@disable_ddl_transaction true] + + assert file3_content =~ + "create unique_index(:posts, [:title], name: \"posts_unique_title_index\")" + + assert file3_content =~ + "create unique_index(:posts, [:name], name: \"posts_unique_name_index\")" + end + test "when an attribute exists only on some of the resources that use the same table, it isn't marked as null: false" do defposts do attributes do From 586c10b87b7776fdbafef955a4bc3733a8385887 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 31 Jan 2025 08:57:37 -0500 Subject: [PATCH 0872/1215] chore: update docs --- lib/mix/tasks/ash_postgres.generate_migrations.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 4a126d24..065942c8 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -22,7 +22,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do * `dry-run` - no files are created, instead the new migration is printed * `check` - no files are created, returns an exit(1) code if the current snapshots and resources don't fit * `snapshots-only` - no migrations are generated, only snapshots are stored - * `concurrent-indexes` - new identities will be run in a separate migration (like concurrent custom indexes) + * `concurrent-indexes` - new identities will be run concurrently and in a separate migration (like concurrent custom indexes) #### Snapshots From 49013012791b5684a1faefd98904a1e3af0f5240 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 31 Jan 2025 10:21:57 -0500 Subject: [PATCH 0873/1215] chore: update test --- test/support/resources/post.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d1082c54..8d1eb3a4 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -750,7 +750,7 @@ defmodule AshPostgres.Test.Post do ) ) - calculate(:start_of_day, :datetime, expr(start_of_day(fragment("now()::timestamp"), "EST"))) + calculate(:start_of_day, :datetime, expr(start_of_day(fragment("now()"), "EST"))) calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) From 0022110afb97d4f01ff3c678b780fa6a8bceee48 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 31 Jan 2025 10:23:58 -0500 Subject: [PATCH 0874/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index e3e41139..976f4819 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.61", "8c34174597ff8d5c0ff8da9a77ad207fb9bce8dfe680fcf1cbf23964d01be739", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e311aab8bf5a1a1bf63ac630a61d218a07de3da16d60f7972e7e85c0eb53d36"}, - "ash_sql": {:hex, :ash_sql, "0.2.49", "9c8a063d0ff9ee5b87716d4719751cee9224aae96ff59258621d3524c2aecdc6", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "681e8c98ea8f23cd406e9ed0ed09822fa7f08eaf9196d8acb092b9815ae0921d"}, + "ash_sql": {:hex, :ash_sql, "0.2.50", "b647825905b6e5195cf26ac90f5980b4dedee040ed3caf6078c0f4e3a80f2b14", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d6a70ffc5d1343aa41ef4804fb3164b4b6a2a135b83075fe89532be9a3b5c370"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From 8fbc109ff2cac4158b801cab97eae7f919b33de8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 3 Feb 2025 09:53:27 -0500 Subject: [PATCH 0875/1215] test: add test for reuse values calculations --- test/calculation_test.exs | 32 ++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 1 + 2 files changed, 33 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 3703ced7..59c2539e 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -4,6 +4,13 @@ defmodule AshPostgres.CalculationTest do require Ash.Query import Ash.Expr + import ExUnit.CaptureLog + + setup do + on_exit(fn -> + Logger.configure(level: :debug) + end) + end test "a calculation that references a first optimizable aggregate can be sorted on" do author1 = @@ -87,6 +94,31 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end + test "expression calculations don't load when `reuse_values?` is true" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + Logger.configure(level: :debug) + + log1 = + capture_log(fn -> + post + |> Ash.load!(:title_twice) + end) + + refute log1 == "" + + log2 = + capture_log(fn -> + post + |> Ash.load!(:title_twice, reuse_values?: true, lazy?: true) + end) + + assert log2 == "" + end + test "an expression calculation can be filtered on" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 8d1eb3a4..efdfbaf7 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -619,6 +619,7 @@ defmodule AshPostgres.Test.Post do calculate(:upper_thing, :string, expr(fragment("UPPER(?)", uniq_on_upper))) calculate(:upper_title, :string, expr(fragment("UPPER(?)", title))) + calculate(:title_twice, :string, expr(title <> title)) calculate( :author_has_post_with_follower_named_fred, From 3a25da75949188ba82de6c2fe11af5b597bf1a5b Mon Sep 17 00:00:00 2001 From: kernel-io Date: Tue, 4 Feb 2025 08:33:48 +1300 Subject: [PATCH 0876/1215] add failing test (#472) Signed-off-by: kernel-io --- test/aggregate_test.exs | 4 ++++ test/support/resources/organization.ex | 6 ++++++ test/support/types/status_enum_no_cast.ex | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index bc2ac0b3..14ff389b 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -12,6 +12,10 @@ defmodule AshSql.AggregateTest do |> Ash.read!() == [] end + test "count aggregate on no cast enum field" do + Organization |> Ash.read!(load: [:no_cast_open_posts_count]) + end + test "relates to actor via has_many and with an aggregate" do org = Organization diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index 6c7346a2..d5a22d65 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -24,6 +24,12 @@ defmodule AshPostgres.Test.Organization do end end + aggregates do + count :no_cast_open_posts_count, :posts do + filter(expr(status_enum_no_cast != :closed)) + end + end + actions do default_accept(:*) diff --git a/test/support/types/status_enum_no_cast.ex b/test/support/types/status_enum_no_cast.ex index 267c2481..d5e0c1e1 100644 --- a/test/support/types/status_enum_no_cast.ex +++ b/test/support/types/status_enum_no_cast.ex @@ -4,5 +4,5 @@ defmodule AshPostgres.Test.Types.StatusEnumNoCast do def storage_type, do: :status - def cast_in_query?, do: false + def cast_in_query?(_), do: false end From 439166ae319b0feff9b60e6dc9d255233a96dada Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 3 Feb 2025 15:36:00 -0500 Subject: [PATCH 0877/1215] chore: add missing @impl --- test/calculation_test.exs | 2 +- test/support/types/status_enum_no_cast.ex | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 59c2539e..93ca0e81 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -8,7 +8,7 @@ defmodule AshPostgres.CalculationTest do setup do on_exit(fn -> - Logger.configure(level: :debug) + Logger.configure(level: :error) end) end diff --git a/test/support/types/status_enum_no_cast.ex b/test/support/types/status_enum_no_cast.ex index d5e0c1e1..ec4a5e6b 100644 --- a/test/support/types/status_enum_no_cast.ex +++ b/test/support/types/status_enum_no_cast.ex @@ -2,7 +2,9 @@ defmodule AshPostgres.Test.Types.StatusEnumNoCast do @moduledoc false use Ash.Type.Enum, values: [:open, :closed] + @impl true def storage_type, do: :status + @impl true def cast_in_query?(_), do: false end From 13ac95e602d6e34ec193ad3c96ee36777ef03afc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 4 Feb 2025 09:44:31 -0500 Subject: [PATCH 0878/1215] chore: update ash_sql --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 976f4819..45bce1db 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.61", "8c34174597ff8d5c0ff8da9a77ad207fb9bce8dfe680fcf1cbf23964d01be739", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e311aab8bf5a1a1bf63ac630a61d218a07de3da16d60f7972e7e85c0eb53d36"}, + "ash": {:hex, :ash, "3.4.62", "ef41463b12095fe9566962793f4e4d94c2753e1c3bfc79acc09471cc1efc7f51", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1780ec2029353213d5a35b1950a53da7225bce756f0be05e35708330f51d9cf0"}, "ash_sql": {:hex, :ash_sql, "0.2.50", "b647825905b6e5195cf26ac90f5980b4dedee040ed3caf6078c0f4e3a80f2b14", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d6a70ffc5d1343aa41ef4804fb3164b4b6a2a135b83075fe89532be9a3b5c370"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.20", "f95227c1cc1e9ee21151d670a3f1bce2151fb2cd7a81fec85a3b832f6ba0d866", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fbc48dfb15ce7cf35e2e688d969ebbaad7ca3291a21a297879169370cc8efef4"}, + "igniter": {:hex, :igniter, "0.5.21", "b80e16a47cb1fe724a2113c1f2661507d9e458978c2d610aeb87b15d9c2d43e5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "19e516b5e06d90447c74fc4fdfc14b71318c41ef966999ac6b34d038e1aa2b9c"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,13 +39,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.1", "d3146087315c4528ee32411495ba10ec88102597b638d4d1455cf9d245dfb57a", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "d7eb9746aa89c40c46b479d6c2a70b82b94993520e40f21d0b09654f23eebf35"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, - "reactor": {:hex, :reactor, "0.11.0", "a985b1b6d60562459a1e820c1cdf74f5408e62089be995ce805d1b30f6762f09", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "19b362159f15871715f6632b4bad6ef0c666b49968e53af73673edabadd0b88b"}, + "reactor": {:hex, :reactor, "0.12.0", "6191b691219c710f7910c275a0ad8853c93f4df99cb1d867321649206231f2e5", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f86335570bb360ef2a403679aa4e94b0f97b97410165d5ae35fb2c5cbf68ddcc"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.43", "4ce84fcc9626f7759e9b9d698c95bdfcbbe9732184e1bc8f6ab9f9963bce2a6a", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "bbd83de69f4436e6f075d632d41110f840ce3a12c34d5499c15e60cf7b45a67b"}, + "spark": {:hex, :spark, "2.2.44", "4333b62fa2c3e9ef1da39792911375063597ca8a960cd677d8e0c2db0064d5b7", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "8a238079c2460a9cbae8e78c43ab2de10bb51a61d7f06e20ae3d3b1291d72ddb"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.8", "289d4eec13e7a83061bc44827877eb4c575e1fdf198bd1a9c6449f9b64805059", [:mix], [], "hexpm", "dbe92fa526589416435e12203b56db1f74c834d207bc474016cedf930d987284"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 0004386e0d8179046fd98da6519c5e3379e46b88 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 4 Feb 2025 11:52:57 -0500 Subject: [PATCH 0879/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 45bce1db..402b9c27 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.62", "ef41463b12095fe9566962793f4e4d94c2753e1c3bfc79acc09471cc1efc7f51", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1780ec2029353213d5a35b1950a53da7225bce756f0be05e35708330f51d9cf0"}, - "ash_sql": {:hex, :ash_sql, "0.2.50", "b647825905b6e5195cf26ac90f5980b4dedee040ed3caf6078c0f4e3a80f2b14", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "d6a70ffc5d1343aa41ef4804fb3164b4b6a2a135b83075fe89532be9a3b5c370"}, + "ash_sql": {:hex, :ash_sql, "0.2.52", "8ca8858f85a4d9a4dcc05deb9f2116b92f939e006e5907e8f97998dfb8d5ff04", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c17207c7169c8ba1d8b5e705be944160b2ad74a780fdb9442202bd1afd6e4549"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From 713b615128dc0a3b978182ed7ef7e1145403dbad Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 5 Feb 2025 14:55:50 -0500 Subject: [PATCH 0880/1215] improvement: consider identity.where in identity deduplicator test: add some calculation related tests --- .../migration_generator.ex | 2 +- mix.lock | 8 +++---- test/calculation_test.exs | 23 ++++++++++++++++++- test/support/resources/post.ex | 21 +++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 98a18c65..baf26c7f 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -645,7 +645,7 @@ defmodule AshPostgres.MigrationGenerator do |> Kernel.!() end) |> Enum.uniq_by(fn identity -> - {identity.keys, identity.base_filter} + {identity.keys, identity.base_filter, identity.where} end) new_snapshot = %{new_snapshot | identities: all_identities} diff --git a/mix.lock b/mix.lock index 402b9c27..d62e7283 100644 --- a/mix.lock +++ b/mix.lock @@ -32,20 +32,20 @@ "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.1", "d3146087315c4528ee32411495ba10ec88102597b638d4d1455cf9d245dfb57a", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "d7eb9746aa89c40c46b479d6c2a70b82b94993520e40f21d0b09654f23eebf35"}, - "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, - "reactor": {:hex, :reactor, "0.12.0", "6191b691219c710f7910c275a0ad8853c93f4df99cb1d867321649206231f2e5", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f86335570bb360ef2a403679aa4e94b0f97b97410165d5ae35fb2c5cbf68ddcc"}, + "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, + "reactor": {:hex, :reactor, "0.12.1", "8bc7b0547c5ada64c9c16ef55f598cea9037d19810c59709d5cede33c5e5b562", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1f26550b4079d46a1b84a153d7427990c2e4adf3dfdbf6ca04c2ea0a9bf1d24d"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.44", "4333b62fa2c3e9ef1da39792911375063597ca8a960cd677d8e0c2db0064d5b7", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "8a238079c2460a9cbae8e78c43ab2de10bb51a61d7f06e20ae3d3b1291d72ddb"}, + "spark": {:hex, :spark, "2.2.45", "19e3a879e80d02853ded85ed7b4c0a84a5d2e395f9d0c884e1a13afbe026929d", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "70b272d0ee16e3c10a4f8cf0ef6152840828152e68f2f8e3046e89567f2b49ad"}, "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, "splode": {:hex, :splode, "0.2.8", "289d4eec13e7a83061bc44827877eb4c575e1fdf198bd1a9c6449f9b64805059", [:mix], [], "hexpm", "dbe92fa526589416435e12203b56db1f74c834d207bc474016cedf930d987284"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 93ca0e81..7f10d0af 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -113,12 +113,33 @@ defmodule AshPostgres.CalculationTest do log2 = capture_log(fn -> post - |> Ash.load!(:title_twice, reuse_values?: true, lazy?: true) + |> Ash.load!(:title_twice, reuse_values?: true) + + assert "in calc:" <> _ = + post + |> Ash.load!(:title_twice_with_calc, reuse_values?: true) + |> Map.get(:title_twice_with_calc) end) assert log2 == "" end + test "calculations use `calculate/3` when possible" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + Logger.configure(level: :debug) + + log = + capture_log(fn -> + assert "in calc:" <> _ = Ash.calculate!(post, :title_twice_with_calc, reuse_values?: true) + end) + + assert log == "" + end + test "an expression calculation can be filtered on" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index efdfbaf7..df5c96cf 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -66,6 +66,26 @@ defmodule AshPostgres.Test.Post do require Ash.Sort + defmodule TitleTwice do + @moduledoc false + use Ash.Resource.Calculation + + def load(_, _, _), do: [:title] + + # it would always be a bug for these + # to produce different values + # but we do it here for testing + def calculate(records, _, _) do + Enum.map(records, fn record -> + "in calc:" <> record.title <> record.title + end) + end + + def expression(_, _) do + expr("in expr:" <> title <> title) + end + end + policies do bypass action_type(:read) do # Check that the post is in the same org as actor @@ -620,6 +640,7 @@ defmodule AshPostgres.Test.Post do calculate(:upper_title, :string, expr(fragment("UPPER(?)", title))) calculate(:title_twice, :string, expr(title <> title)) + calculate(:title_twice_with_calc, :string, TitleTwice) calculate( :author_has_post_with_follower_named_fred, From 33b1291c594d936bdf4e561d1af1ecb0d16d9fa8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 5 Feb 2025 15:34:47 -0500 Subject: [PATCH 0881/1215] test: fix tests to account for ash bulk action fix --- test/bulk_create_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index f4e7ceb6..01769ccb 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -295,7 +295,7 @@ defmodule AshPostgres.BulkCreateTest do describe "validation errors" do test "skips invalid by default" do assert %{records: [_], errors: [_]} = - Ash.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, + Ash.bulk_create([%{title: "fred"}, %{title: "not allowed"}], Post, :create, return_records?: true, return_errors?: true ) From 05b1fd3dba454570f74001cdd3b87d8482fdb591 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 5 Feb 2025 15:40:11 -0500 Subject: [PATCH 0882/1215] test: fix test log level issue --- test/calculation_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 7f10d0af..97d5a3c4 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -8,7 +8,7 @@ defmodule AshPostgres.CalculationTest do setup do on_exit(fn -> - Logger.configure(level: :error) + Logger.configure(level: :warning) end) end From b93c3b54feb7b615e3878a61b867418f8ecb9a30 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 5 Feb 2025 15:58:12 -0500 Subject: [PATCH 0883/1215] chore: only dev/test for tz --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 4ad5f2be..7eda5369 100644 --- a/mix.exs +++ b/mix.exs @@ -173,7 +173,7 @@ defmodule AshPostgres.MixProject do {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies - {:tz, "~> 0.28.1"}, + {:tz, "~> 0.28.1", only: [:dev, :test]}, {:ecto_dev_logger, "~> 0.14", only: :test}, {:eflame, "~> 1.0", only: [:dev, :test]}, {:simple_sat, "~> 0.1", only: [:dev, :test]}, From 7bd5a41f2cd8321c86b70434f290356e7a9c43a9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 5 Feb 2025 16:20:14 -0500 Subject: [PATCH 0884/1215] fix: simplify lateral join source filter --- lib/data_layer.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 274fd495..30a502ea 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1316,6 +1316,9 @@ defmodule AshPostgres.DataLayer do [] -> raise "Cannot use lateral joins with a resource that has no primary key and no identities" + [key] -> + Ash.Expr.expr(^Ash.Expr.ref(key) in ^Enum.map(records, &Map.get(&1, key))) + keys -> Enum.reduce(records, Ash.Expr.expr(false), fn record, filter_expr -> all_keys_match_expr = From e4bd2272d60077847a7f26fcc27f5dc4096b0cba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:08:50 -0500 Subject: [PATCH 0885/1215] chore(deps): bump ash_sql in the production-dependencies group (#476) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.52 to 0.2.53 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.52...v0.2.53) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index d62e7283..b43360ac 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.62", "ef41463b12095fe9566962793f4e4d94c2753e1c3bfc79acc09471cc1efc7f51", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1780ec2029353213d5a35b1950a53da7225bce756f0be05e35708330f51d9cf0"}, - "ash_sql": {:hex, :ash_sql, "0.2.52", "8ca8858f85a4d9a4dcc05deb9f2116b92f939e006e5907e8f97998dfb8d5ff04", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c17207c7169c8ba1d8b5e705be944160b2ad74a780fdb9442202bd1afd6e4549"}, + "ash_sql": {:hex, :ash_sql, "0.2.53", "522a0829410ef5a35f172d7da089549b9f071b32d228283daebef921dba9c89b", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a384206185192421f40f517125ea9429c76db6bf177d7b268870f21dbcf32f8b"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From 9f6f7ba9bf5ea9107df009c7ece1ef3d812bde5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:09:09 -0500 Subject: [PATCH 0886/1215] chore(deps-dev): bump ex_doc in the dev-dependencies group (#477) Bumps the dev-dependencies group with 1 update: [ex_doc](https://github.com/elixir-lang/ex_doc). Updates `ex_doc` from 0.37.0-rc.2 to 0.37.0 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.0-rc.2...v0.37.0) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index b43360ac..a6d0a6d9 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.37.0-rc.2", "6e55e065aea63c2dfb3c0e18786a22d5107923ff7fb6a91f6e575f607735d09b", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "91e09b4ca47b2a83ce4c7035de7f0bb6e531c5d43cab19bb0c7820f73470df49"}, + "ex_doc": {:hex, :ex_doc, "0.37.0", "970f92b39e62c460aa8a367508e938f5e4da6e2ff3eaed3f8530b25870f45471", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "b0ee7f17373948e0cf471e59c3a0ee42f3bd1171c67d91eb3626456ef9c6202c"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, From c3d0326a52732de6de05c57bd0cb9fd0a018e6e0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Feb 2025 20:59:38 -0500 Subject: [PATCH 0887/1215] fix: update lateral join logic to match ash_sql's test: update tests for tz calculation --- lib/data_layer.ex | 44 ++++++++++++++++++++++++++++------ test/calculation_test.exs | 8 ++++--- test/support/resources/post.ex | 2 +- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 30a502ea..bd1992d3 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1015,7 +1015,7 @@ defmodule AshPostgres.DataLayer do defp lateral_join_query( query, root_data, - [{source_query, source_attribute, destination_attribute, relationship}] + [{source_query, source_attribute, destination_attribute, relationship}] = path ) do source_query = Ash.Query.new(source_query) @@ -1065,7 +1065,7 @@ defmodule AshPostgres.DataLayer do source_pkey = Ash.Resource.Info.primary_key(source_query.resource) - case lateral_join_source_query(query, source_query, root_data) do + case lateral_join_source_query(query, source_query, root_data, path) do {:ok, data_layer_query} -> source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) @@ -1148,13 +1148,13 @@ defmodule AshPostgres.DataLayer do {source_query, source_attribute, source_attribute_on_join_resource, relationship}, {through_resource, destination_attribute_on_join_resource, destination_attribute, through_relationship} - ] + ] = path ) do source_query = Ash.Query.new(source_query) source_values = Enum.map(root_data, &Map.get(&1, source_attribute)) source_pkey = Ash.Resource.Info.primary_key(source_query.resource) - case lateral_join_source_query(query, source_query, root_data) do + case lateral_join_source_query(query, source_query, root_data, path) do {:ok, data_layer_query} -> data_layer_query = Ecto.Query.exclude(data_layer_query, :select) @@ -1275,7 +1275,8 @@ defmodule AshPostgres.DataLayer do } }, source_query, - _root_data + _root_data, + _path ) when not is_nil(lateral_join_source_query) do {:ok, @@ -1283,15 +1284,44 @@ defmodule AshPostgres.DataLayer do |> set_subquery_prefix(source_query, lateral_join_source_query.__ash_bindings__.resource)} end - defp lateral_join_source_query(query, source_query, root_data) do + defp lateral_join_source_query(query, source_query, root_data, path) do source_query.resource |> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]}) + |> Ash.Query.set_context(%{ + :data_layer => + Map.put( + source_query.context[:data_layer] || %{}, + :no_inner_join?, + true + ) + }) |> Ash.Query.set_tenant(source_query.tenant) |> filter_for_records(root_data) |> set_lateral_join_prefix(query) |> case do %{valid?: true} = query -> - Ash.Query.data_layer_query(query) + relationship = path |> List.first() |> elem(3) + + {:ok, expr} = + Ash.Filter.hydrate_refs(relationship.filter, %{ + resource: relationship.destination, + parent_stack: [relationship.source] + }) + + parent_expr = AshSql.Join.parent_expr(expr) + + used_aggregates = + Ash.Filter.used_aggregates(parent_expr, []) + + with {:ok, query} <- Ash.Query.data_layer_query(query) do + AshSql.Aggregate.add_aggregates( + query, + used_aggregates, + relationship.source, + false, + query.__ash_bindings__.root_binding + ) + end query -> {:error, query} diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 97d5a3c4..b84e9732 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -51,9 +51,11 @@ defmodule AshPostgres.CalculationTest do |> Ash.read!() end - test "start_of_day functions the same as Elixir's start of ay" do - assert Ash.calculate!(Post, :start_of_day) == - Ash.Expr.eval!(Ash.Expr.expr(start_of_day(^DateTime.utc_now(), "EST"))) + test "start_of_day functions the same as Elixir's start of day" do + Logger.configure(level: :debug) + + assert Ash.calculate!(Post, :start_of_day, data_layer?: true) == + Ash.Expr.eval!(Ash.Expr.expr(start_of_day(now(), "EST"))) end @tag :regression diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index df5c96cf..f87e6dc2 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -772,7 +772,7 @@ defmodule AshPostgres.Test.Post do ) ) - calculate(:start_of_day, :datetime, expr(start_of_day(fragment("now()"), "EST"))) + calculate(:start_of_day, :datetime, expr(start_of_day(now(), "EST"))) calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) From 95c674b7c417e35015c9359d00690245393e5a50 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 10 Feb 2025 17:00:56 -0500 Subject: [PATCH 0888/1215] improvement: add vector l2 distance function improvement: use dimenstions constraint on vector for size --- lib/data_layer.ex | 3 ++- lib/functions/vector_cosine_distance.ex | 2 ++ lib/functions/vector_l2_distance.ex | 11 ++++++++++ .../migration_generator.ex | 8 ++++++++ lib/sql_implementation.ex | 20 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 lib/functions/vector_l2_distance.ex diff --git a/lib/data_layer.ex b/lib/data_layer.ex index bd1992d3..274919e3 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -820,7 +820,8 @@ defmodule AshPostgres.DataLayer do if "vector" in (config[:installed_extensions] || []) do functions ++ [ - AshPostgres.Functions.VectorCosineDistance + AshPostgres.Functions.VectorCosineDistance, + AshPostgres.Functions.VectorL2Distance ] else functions diff --git a/lib/functions/vector_cosine_distance.ex b/lib/functions/vector_cosine_distance.ex index f2e20d3a..349c1977 100644 --- a/lib/functions/vector_cosine_distance.ex +++ b/lib/functions/vector_cosine_distance.ex @@ -6,4 +6,6 @@ defmodule AshPostgres.Functions.VectorCosineDistance do use Ash.Query.Function, name: :vector_cosine_distance def args, do: [[:vector, :vector]] + + def returns, do: [:float] end diff --git a/lib/functions/vector_l2_distance.ex b/lib/functions/vector_l2_distance.ex new file mode 100644 index 00000000..c3f13ca0 --- /dev/null +++ b/lib/functions/vector_l2_distance.ex @@ -0,0 +1,11 @@ +defmodule AshPostgres.Functions.VectorL2Distance do + @moduledoc """ + Maps to the vector l2 distance operator. Requires `vector` extension to be installed. + """ + + use Ash.Query.Function, name: :vector_l2_distance + + def args, do: [[:vector, :vector]] + + def returns, do: [:float] +end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index baf26c7f..584bc5c0 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2959,6 +2959,14 @@ defmodule AshPostgres.MigrationGenerator do defp migration_type(Ash.Type.UUIDv7, _), do: :uuid defp migration_type(Ash.Type.Integer, _), do: :bigint + defp migration_type(Ash.Type.Vector, constraints) do + if constraints[:dimensions] do + {:vector, constraints[:dimensions]} + else + :vector + end + end + defp migration_type(other, constraints) do type = Ash.Type.get_type(other) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 15a50bfd..8f530861 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -153,6 +153,26 @@ defmodule AshPostgres.SqlImplementation do {:ok, Ecto.Query.dynamic(fragment("(? <=> ?)", ^arg1, ^arg2)), acc} end + def expr( + query, + %AshPostgres.Functions.VectorL2Distance{ + arguments: [arg1, arg2], + embedded?: pred_embedded? + }, + bindings, + embedded?, + acc, + _type + ) do + {arg1, acc} = + AshSql.Expr.dynamic_expr(query, arg1, bindings, pred_embedded? || embedded?, :string, acc) + + {arg2, acc} = + AshSql.Expr.dynamic_expr(query, arg2, bindings, pred_embedded? || embedded?, :string, acc) + + {:ok, Ecto.Query.dynamic(fragment("(? <-> ?)", ^arg1, ^arg2)), acc} + end + def expr( query, %Ash.Query.Ref{ From d70c5c2fa43392257c4eec8522fd73a168717007 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 11 Feb 2025 09:59:04 -0500 Subject: [PATCH 0889/1215] chore: update deps --- mix.lock | 14 +++++++------- test/calculation_test.exs | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mix.lock b/mix.lock index a6d0a6d9..c49a0fd9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.62", "ef41463b12095fe9566962793f4e4d94c2753e1c3bfc79acc09471cc1efc7f51", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1780ec2029353213d5a35b1950a53da7225bce756f0be05e35708330f51d9cf0"}, - "ash_sql": {:hex, :ash_sql, "0.2.53", "522a0829410ef5a35f172d7da089549b9f071b32d228283daebef921dba9c89b", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a384206185192421f40f517125ea9429c76db6bf177d7b268870f21dbcf32f8b"}, + "ash": {:hex, :ash, "3.4.63", "7bf24d7e40039bf82652975460dca55e3d23fdb77738ebf665c16b7a4519b86e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "10175209ca1f01237959ce1ba0638b04c8d5a231dc0c128b2eea431ee32699e8"}, + "ash_sql": {:hex, :ash_sql, "0.2.54", "25f8c0aab413d31f7c795c158c52fb79487b6252c897e15d94ff7faabd8c7246", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5cd87c166260e661854efa3eb03dd91f3358d2e1b93e50ab07cbce21e33105e6"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.21", "b80e16a47cb1fe724a2113c1f2661507d9e458978c2d610aeb87b15d9c2d43e5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "19e516b5e06d90447c74fc4fdfc14b71318c41ef966999ac6b34d038e1aa2b9c"}, + "igniter": {:hex, :igniter, "0.5.22", "af4deba45c9c0ffdff257f9730fcfb6322e785b030b4982550affe0aaabfa3fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "601b639bbf3a4740335a91b038999c3381ff9371f07122d0640b820cd2d5cfe3"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -37,19 +37,19 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "owl": {:hex, :owl, "0.12.1", "d3146087315c4528ee32411495ba10ec88102597b638d4d1455cf9d245dfb57a", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "d7eb9746aa89c40c46b479d6c2a70b82b94993520e40f21d0b09654f23eebf35"}, + "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.12.1", "8bc7b0547c5ada64c9c16ef55f598cea9037d19810c59709d5cede33c5e5b562", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1f26550b4079d46a1b84a153d7427990c2e4adf3dfdbf6ca04c2ea0a9bf1d24d"}, + "reactor": {:hex, :reactor, "0.13.0", "072570a78e4cafcb81b81f143fc37f534ab7b206b2c5b445480638468bf59637", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "72aadf43584bab94b1907bff9371ac324acebd28f095f4320dd14dee2b499319"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, "spark": {:hex, :spark, "2.2.45", "19e3a879e80d02853ded85ed7b4c0a84a5d2e395f9d0c884e1a13afbe026929d", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "70b272d0ee16e3c10a4f8cf0ef6152840828152e68f2f8e3046e89567f2b49ad"}, - "spitfire": {:hex, :spitfire, "0.1.4", "8fe0df66e735323e4f2a56e719603391b160dd68efd922cadfbb85a2cf6c68af", [:mix], [], "hexpm", "d40d850f4ede5235084876246756b90c7bcd12994111d57c55e2e1e23ac3fe61"}, + "spitfire": {:hex, :spitfire, "0.1.5", "10b041e781bff9544d2fdf00893e1a325758408c5366a9bfa4333072568659b1", [:mix], [], "hexpm", "866a55d21fe827934ff38200111335c9dd311df13cbf2580ed71d84b0a783150"}, "splode": {:hex, :splode, "0.2.8", "289d4eec13e7a83061bc44827877eb4c575e1fdf198bd1a9c6449f9b64805059", [:mix], [], "hexpm", "dbe92fa526589416435e12203b56db1f74c834d207bc474016cedf930d987284"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, + "stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index b84e9732..70f9b11d 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -52,8 +52,6 @@ defmodule AshPostgres.CalculationTest do end test "start_of_day functions the same as Elixir's start of day" do - Logger.configure(level: :debug) - assert Ash.calculate!(Post, :start_of_day, data_layer?: true) == Ash.Expr.eval!(Ash.Expr.expr(start_of_day(now(), "EST"))) end From c293359353aa0d8865511dc56c40e54c72bea0bb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 11 Feb 2025 09:59:21 -0500 Subject: [PATCH 0890/1215] chore: release version v2.5.2 --- CHANGELOG.md | 23 +++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc0c85a..658758ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.2](https://github.com/ash-project/ash_postgres/compare/v2.5.1...v2.5.2) (2025-02-11) + + + + +### Bug Fixes: + +* update lateral join logic to match ash_sql's + +* simplify lateral join source filter + +* update sql log switches for migration and rollback tasks (#470) + +### Improvements: + +* add vector l2 distance function + +* use dimenstions constraint on vector for size + +* consider identity.where in identity deduplicator + +* generate migrations task support concurrent indexes flag (#471) + ## [v2.5.1](https://github.com/ash-project/ash_postgres/compare/v2.5.0...v2.5.1) (2025-01-27) diff --git a/mix.exs b/mix.exs index 7eda5369..bb70edca 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.1" + @version "2.5.2" def project do [ From cfdbe79c8f044098bf7ae4c185bceecbad2c9ddb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 11 Feb 2025 11:07:01 -0500 Subject: [PATCH 0891/1215] chore: update start_of_day calc --- test/support/resources/post.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index f87e6dc2..3fb6e103 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -772,7 +772,11 @@ defmodule AshPostgres.Test.Post do ) ) - calculate(:start_of_day, :datetime, expr(start_of_day(now(), "EST"))) + calculate( + :start_of_day, + :datetime, + expr(start_of_day(fragment("now() AT TIME ZONE 'UTC'"), "EST")) + ) calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) From a84358ce0c9c525e285640300d455257602b6c5b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 11 Feb 2025 11:10:11 -0500 Subject: [PATCH 0892/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c49a0fd9..1fcbc77a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.63", "7bf24d7e40039bf82652975460dca55e3d23fdb77738ebf665c16b7a4519b86e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "10175209ca1f01237959ce1ba0638b04c8d5a231dc0c128b2eea431ee32699e8"}, - "ash_sql": {:hex, :ash_sql, "0.2.54", "25f8c0aab413d31f7c795c158c52fb79487b6252c897e15d94ff7faabd8c7246", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5cd87c166260e661854efa3eb03dd91f3358d2e1b93e50ab07cbce21e33105e6"}, + "ash_sql": {:hex, :ash_sql, "0.2.56", "3d438a18af5d7c39e0b2b2ba6b6adc8b2abe40d9caa18af720433fc3126efac1", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "22296a2afe1f69ec1c2de7490f6821a8551c16c914e746663458068a7808a9d4"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From d21f06ca19b22dfd6231ceb4f9af990b16c44478 Mon Sep 17 00:00:00 2001 From: Maciej Malecki Date: Wed, 12 Feb 2025 21:51:13 +0100 Subject: [PATCH 0893/1215] fix: Ignore module conflict when compiling migration file (#482) This fixes the problem of logging "redefining module" entries when using context-driven multitenancy (where each tenant tries to compile the migration module). Fixes #480 --- lib/migration_compile_cache.ex | 3 +++ lib/multitenancy.ex | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/migration_compile_cache.ex b/lib/migration_compile_cache.ex index c8f9108a..802c21ad 100644 --- a/lib/migration_compile_cache.ex +++ b/lib/migration_compile_cache.ex @@ -28,11 +28,14 @@ defmodule AshPostgres.MigrationCompileCache do defp ensure_compiled(state, file) do case Map.get(state, file) do nil -> + Code.put_compiler_option(:ignore_module_conflict, true) compiled = Code.compile_file(file) Map.put(state, file, compiled) _ -> state end + after + Code.put_compiler_option(:ignore_module_conflict, false) end end diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 505c9cb0..20e67c0c 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -15,8 +15,6 @@ defmodule AshPostgres.MultiTenancy do migrations_path || repo.config()[:tenant_migrations_path] || default_tenant_migration_path(repo) - Code.compiler_options(ignore_module_conflict: true) - Ecto.Migration.SchemaMigration.ensure_schema_migrations_table!( repo, repo.config(), @@ -44,8 +42,6 @@ defmodule AshPostgres.MultiTenancy do Ecto.Migration.SchemaMigration.up(repo, repo.config(), version, prefix: tenant_name) end) - after - Code.compiler_options(ignore_module_conflict: false) end # sobelow_skip ["SQL"] From d3788e35ca54c2cc53f23dd5dabe9ffbab7a2af1 Mon Sep 17 00:00:00 2001 From: quartz Date: Wed, 12 Feb 2025 21:55:17 +0100 Subject: [PATCH 0894/1215] test: custom types in expr (#481) --- test/type_test.exs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/type_test.exs b/test/type_test.exs index 53c073e4..beebbe82 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -35,4 +35,20 @@ defmodule AshPostgres.Test.TypeTest do |> Ash.Query.filter(fragment("? = ?", id, type(^uuid, :uuid))) |> Ash.read!() end + + test "complex custom types can be used in filters" do + Post + |> Ash.Changeset.for_create(:create, %{point: {1.0, 2.0, 3.0}, composite_point: %{x: 1, y: 2}}) + |> Ash.create!() + + assert [_] = + Post + |> Ash.Query.filter(composite_point == %{x: 1, y: 2}) + |> Ash.read!() + + assert [_] = + Post + |> Ash.Query.filter(point == {1.0, 2.0, 3.0}) + |> Ash.read!() + end end From 68fddacd3bacd1a1b5f8edc805a757c90d30ff2c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 12 Feb 2025 16:44:58 -0500 Subject: [PATCH 0895/1215] chore: update test --- test/type_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/type_test.exs b/test/type_test.exs index beebbe82..856a2da3 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -48,7 +48,7 @@ defmodule AshPostgres.Test.TypeTest do assert [_] = Post - |> Ash.Query.filter(point == {1.0, 2.0, 3.0}) + |> Ash.Query.filter(point == ^{1.0, 2.0, 3.0}) |> Ash.read!() end end From 5313f466bb46a894b663bd9ac89b147deeb9aae6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 08:46:47 -0500 Subject: [PATCH 0896/1215] chore(deps-dev): bump ex_doc in the dev-dependencies group (#484) Bumps the dev-dependencies group with 1 update: [ex_doc](https://github.com/elixir-lang/ex_doc). Updates `ex_doc` from 0.37.0 to 0.37.1 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.0...v0.37.1) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 1fcbc77a..f483186e 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.37.0", "970f92b39e62c460aa8a367508e938f5e4da6e2ff3eaed3f8530b25870f45471", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "b0ee7f17373948e0cf471e59c3a0ee42f3bd1171c67d91eb3626456ef9c6202c"}, + "ex_doc": {:hex, :ex_doc, "0.37.1", "65ca30d242082b95aa852b3b73c9d9914279fff56db5dc7b3859be5504417980", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "6774f75477733ea88ce861476db031f9399c110640752ca2b400dbbb50491224"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, From 8936bc1f2a85f89e3bb4ffc4ca9d0451e71ebbb7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 13 Feb 2025 23:34:40 -0500 Subject: [PATCH 0897/1215] fix: handle dropping primary key columns properly --- .../migration_generator.ex | 138 ++++++++++++++++-- lib/migration_generator/operation.ex | 62 ++++++++ 2 files changed, 187 insertions(+), 13 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 584bc5c0..bd9dd1f7 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1298,6 +1298,12 @@ defmodule AshPostgres.MigrationGenerator do defp after?(%Operation.RemovePrimaryKeyDown{}, _), do: true defp after?(_, %Operation.RemovePrimaryKeyDown{}), do: false + defp after?(%Operation.AddPrimaryKeyDown{}, _), do: false + defp after?(_, %Operation.AddPrimaryKeyDown{}), do: true + + defp after?(%Operation.AddPrimaryKey{}, _), do: true + defp after?(_, %Operation.AddPrimaryKey{}), do: false + defp after?( %Operation.AddCustomStatement{}, _ @@ -1803,7 +1809,9 @@ defmodule AshPostgres.MigrationGenerator do defp do_fetch_operations(snapshot, old_snapshot, opts, acc) do attribute_operations = attribute_operations(snapshot, old_snapshot, opts) - pkey_operations = pkey_operations(snapshot, old_snapshot, attribute_operations, opts) + + {pkey_operations, attribute_operations} = + pkey_operations(snapshot, old_snapshot, attribute_operations, opts) rewrite_all_identities? = changing_multitenancy_affects_identities?(snapshot, old_snapshot) @@ -2097,7 +2105,7 @@ defmodule AshPostgres.MigrationGenerator do defp pkey_operations(snapshot, old_snapshot, attribute_operations, opts) do if old_snapshot[:empty?] do - [] + {[], attribute_operations} else must_drop_pkey? = Enum.any?( @@ -2115,11 +2123,38 @@ defmodule AshPostgres.MigrationGenerator do } -> true + %Operation.RemoveAttribute{ + attribute: %{primary_key?: true} + } -> + true + _ -> false end ) + must_add_primary_key? = + must_drop_pkey? && + Enum.any?(snapshot.attributes, fn attribute -> + attribute.primary_key? + end) + + must_add_primary_key_in_down? = + must_drop_pkey? && + Enum.any?(snapshot.attributes, fn attribute -> + attribute.primary_key? && + !Enum.any?(attribute_operations, fn + %Operation.AlterAttribute{} = operation -> + operation.new_attribute.source == attribute.source + + %Operation.AddAttribute{} = operation -> + operation.attribute.source == attribute.source + + _ -> + false + end) + end) + drop_in_down? = Enum.any?(attribute_operations, fn %Operation.AlterAttribute{ @@ -2148,17 +2183,94 @@ defmodule AshPostgres.MigrationGenerator do false end) - [ - must_drop_pkey? && - %Operation.RemovePrimaryKey{schema: snapshot.schema, table: snapshot.table}, - must_drop_pkey? && drop_in_down? && - %Operation.RemovePrimaryKeyDown{ - commented?: opts.dont_drop_columns && drop_in_down_commented?, - schema: snapshot.schema, - table: snapshot.table - } - ] - |> Enum.filter(& &1) + attribute_operations = + if must_add_primary_key? do + Enum.map( + attribute_operations, + fn + %Operation.AlterAttribute{} = operation -> + %{ + operation + | new_attribute: %{ + operation.new_attribute + | primary_key?: operation.old_attribute.primary_key? + } + } + + %Operation.AddAttribute{} = operation -> + %{operation | attribute: %{operation.attribute | primary_key?: false}} + + other -> + other + end + ) + else + attribute_operations + end + + attribute_operations = + if must_add_primary_key_in_down? do + Enum.map( + attribute_operations, + fn + %Operation.AlterAttribute{} = operation -> + %{ + operation + | old_attribute: %{ + operation.old_attribute + | primary_key?: operation.new_attribute.primary_key? + } + } + + %Operation.RemoveAttribute{} = operation -> + %{operation | attribute: %{operation.attribute | primary_key?: false}} + + other -> + other + end + ) + else + attribute_operations + end + + {[ + must_drop_pkey? && + %Operation.RemovePrimaryKey{schema: snapshot.schema, table: snapshot.table}, + must_drop_pkey? && drop_in_down? && + %Operation.RemovePrimaryKeyDown{ + commented?: opts.dont_drop_columns && drop_in_down_commented?, + schema: snapshot.schema, + table: snapshot.table + }, + must_add_primary_key? && + %Operation.AddPrimaryKey{ + schema: snapshot.schema, + table: snapshot.table, + keys: + Enum.flat_map(snapshot.attributes, fn attribute -> + if attribute.primary_key? do + [attribute.source] + else + [] + end + end) + }, + must_add_primary_key_in_down? && + %Operation.AddPrimaryKeyDown{ + schema: old_snapshot.schema, + table: old_snapshot.table, + remove_old?: must_add_primary_key? && !(must_drop_pkey? && drop_in_down?), + keys: + Enum.flat_map(old_snapshot.attributes, fn attribute -> + if attribute.primary_key? do + [attribute.source] + else + [] + end + end) + } + ] + |> Enum.filter(& &1), attribute_operations} end end diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index cc4f1975..4b8c4eae 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1051,6 +1051,68 @@ defmodule AshPostgres.MigrationGenerator.Operation do end end + defmodule AddPrimaryKey do + @moduledoc false + defstruct [:schema, :table, :keys, no_phase: true] + + def up(%{schema: schema, table: table, keys: keys}) do + keys = Enum.join(keys, ", ") + + if schema do + """ + execute("ALTER TABLE \\\"#{schema}.#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + else + """ + execute("ALTER TABLE \\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + end + end + + def down(_) do + "" + end + end + + defmodule AddPrimaryKeyDown do + @moduledoc false + defstruct [:schema, :table, :keys, :remove_old?, no_phase: true] + + def up(_) do + "" + end + + def down(%{schema: schema, table: table, remove_old?: remove_old?, keys: keys}) do + keys = Enum.join(keys, ", ") + + if schema do + remove_old = + if remove_old? do + """ + execute("ALTER TABLE \\\"#{schema}.#{table}\\\" DROP constraint #{table}_pkey") + """ + end + + """ + #{remove_old} + execute("ALTER TABLE \\\"#{schema}.#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + else + remove_old = + if remove_old? do + """ + execute("ALTER TABLE \\\"#{table}\\\" DROP constraint #{table}_pkey") + """ + end + + """ + #{remove_old} + execute("ALTER TABLE \\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + end + end + end + defmodule RemovePrimaryKey do @moduledoc false defstruct [:schema, :table, no_phase: true] From e19c93a62c1377b93bddea00931175a8b1fc7f3d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 14 Feb 2025 00:14:06 -0500 Subject: [PATCH 0898/1215] chore: fix tests --- .../migration_generator.ex | 12 ++- test/migration_generator_test.exs | 99 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index bd9dd1f7..e0301956 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2136,7 +2136,17 @@ defmodule AshPostgres.MigrationGenerator do must_add_primary_key? = must_drop_pkey? && Enum.any?(snapshot.attributes, fn attribute -> - attribute.primary_key? + attribute.primary_key? && + !Enum.any?(attribute_operations, fn + %Operation.AlterAttribute{} = operation -> + operation.new_attribute.source == attribute.source + + %Operation.AddAttribute{} = operation -> + operation.attribute.source == attribute.source + + _ -> + false + end) end) must_add_primary_key_in_down? = diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 131f8c4a..b7f10688 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -393,6 +393,105 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "creating follow up migrations with a composite primary key" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + + defposts do + postgres do + schema("example") + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true, primary_key?: true, allow_nil?: false) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + :ok + end + + test "when removing an element, it recreates the primary key" do + defposts do + postgres do + schema("example") + end + + attributes do + uuid_primary_key(:id) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + contents = File.read!(file2) + + [up_side, down_side] = String.split(contents, "def down", parts: 2) + + assert up_side =~ ~S[execute("ALTER TABLE \"example.posts\" ADD PRIMARY KEY (id)")] + assert down_side =~ ~S[execute("ALTER TABLE \"example.posts\" DROP constraint posts_pkey")] + assert down_side =~ ~S[execute("ALTER TABLE \"example.posts\" ADD PRIMARY KEY (id, title)")] + + defposts do + postgres do + schema("example") + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true, primary_key?: true, allow_nil?: false) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, _file2, file3] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + contents = File.read!(file3) + + [up_side, down_side] = String.split(contents, "def down", parts: 2) + + assert up_side =~ ~S[execute("ALTER TABLE \"example.posts\" ADD PRIMARY KEY (id, title)")] + assert down_side =~ ~S[execute("ALTER TABLE \"example.posts\" ADD PRIMARY KEY (id)")] + end + end + describe "creating follow up migrations with a schema" do setup do on_exit(fn -> From d82af2374339e38df8f5f8afcbab53fa40deb147 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 14 Feb 2025 00:21:20 -0500 Subject: [PATCH 0899/1215] chore: release version v2.5.3 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 658758ce..62f756d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.3](https://github.com/ash-project/ash_postgres/compare/v2.5.2...v2.5.3) (2025-02-14) + + + + +### Bug Fixes: + +* handle dropping primary key columns properly + +* Ignore module conflict when compiling migration file (#482) + ## [v2.5.2](https://github.com/ash-project/ash_postgres/compare/v2.5.1...v2.5.2) (2025-02-11) diff --git a/mix.exs b/mix.exs index bb70edca..27170ce8 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.2" + @version "2.5.3" def project do [ From c2b2a7d43ca369e80fe05c15ea69d4cff185bf85 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 01:11:20 -0500 Subject: [PATCH 0900/1215] chore: add test for map atomic updates --- .../test_repo/posts/20250217054207.json | 509 ++++++++++++++++++ .../20250217054207_migrate_resources49.exs | 21 + test/bulk_update_test.exs | 8 + test/support/resources/post.ex | 5 + 4 files changed, 543 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20250217054207.json create mode 100644 priv/test_repo/migrations/20250217054207_migrate_resources49.exs diff --git a/priv/resource_snapshots/test_repo/posts/20250217054207.json b/priv/resource_snapshots/test_repo/posts/20250217054207.json new file mode 100644 index 00000000..47298089 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250217054207.json @@ -0,0 +1,509 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "metadata", + "type": "map" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "65F28CF9B30958792FAE16BED984833E82C5461CCC9AD8814C18B871BAAE4E1B", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250217054207_migrate_resources49.exs b/priv/test_repo/migrations/20250217054207_migrate_resources49.exs new file mode 100644 index 00000000..19f6424f --- /dev/null +++ b/priv/test_repo/migrations/20250217054207_migrate_resources49.exs @@ -0,0 +1,21 @@ +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(:posts) do + add(:metadata, :map) + end + end + + def down do + alter table(:posts) do + remove(:metadata) + end + end +end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 6ed482df..201595a7 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -20,6 +20,14 @@ defmodule AshPostgres.BulkUpdateTest do assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff")) end + @tag :regression + test "bulk updates can update maps with the join strategy" do + Post + |> Ash.Query.limit(1) + |> Ash.Query.filter(exists(comments, title == parent(title))) + |> Ash.bulk_update!(:update_metadata, %{metadata: %{1 => 2}}) + end + test "bulk updates can set datetimes" do Post |> Ash.Changeset.for_create(:create, %{title: "fred"}) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 3fb6e103..26fae521 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -250,6 +250,10 @@ defmodule AshPostgres.Test.Post do ) end + update :update_metadata do + accept([:metadata]) + end + destroy :cascade_destroy do change(cascade_destroy(:high_ratings, after_action?: false)) end @@ -410,6 +414,7 @@ defmodule AshPostgres.Test.Post do attribute(:decimal, :decimal, default: Decimal.new(0), public?: true) attribute(:status, AshPostgres.Test.Types.Status, public?: true) attribute(:status_enum, AshPostgres.Test.Types.StatusEnum, public?: true) + attribute(:metadata, :map) attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast, source: :status_enum, From 64d768c79ce3f96054a1b6920d9f854390e2f9d8 Mon Sep 17 00:00:00 2001 From: Lucas Mendelowski Date: Mon, 17 Feb 2025 19:11:15 +0100 Subject: [PATCH 0901/1215] improvement: Add support for field names in idenitity constraints (#478) --- lib/data_layer.ex | 33 ++++++---- mix.exs | 4 +- .../test_repo/orgs/20250210191116.json | 64 +++++++++++++++++++ .../20250210191116_migrate_resources49.exs | 25 ++++++++ test/support/resources/organization.ex | 11 ++++ test/unique_identity_test.exs | 14 ++++ 6 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/orgs/20250210191116.json create mode 100644 priv/test_repo/migrations/20250210191116_migrate_resources49.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 274919e3..a073e5ea 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -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 [] -> @@ -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 @@ -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 -> @@ -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( diff --git a/mix.exs b/mix.exs index 27170ce8..fe528021 100644 --- a/mix.exs +++ b/mix.exs @@ -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"}, @@ -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] version when is_binary(version) -> "~> #{version}" diff --git a/priv/resource_snapshots/test_repo/orgs/20250210191116.json b/priv/resource_snapshots/test_repo/orgs/20250210191116.json new file mode 100644 index 00000000..61cc82a1 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orgs/20250210191116.json @@ -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" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250210191116_migrate_resources49.exs b/priv/test_repo/migrations/20250210191116_migrate_resources49.exs new file mode 100644 index 00000000..622dddc6 --- /dev/null +++ b/priv/test_repo/migrations/20250210191116_migrate_resources49.exs @@ -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 diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index d5a22d65..0013f909 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -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 @@ -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 diff --git a/test/unique_identity_test.exs b/test/unique_identity_test.exs index e210ac77..8fae1b98 100644 --- a/test/unique_identity_test.exs +++ b/test/unique_identity_test.exs @@ -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 @@ -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 From 5377c15447d3fe16b57512717e74bc3df4a52eeb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 14:39:27 -0500 Subject: [PATCH 0902/1215] chore: format/test --- lib/data_layer.ex | 14 ++++++++++---- test/calculation_test.exs | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index a073e5ea..dfa561c0 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2372,7 +2372,12 @@ defmodule AshPostgres.DataLayer do {:error, Ash.Error.to_ash_error(error, stacktrace)} end - defp constraints_to_errors(%{constraints: user_constraints} = changeset, action, constraints, resource) 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 -> @@ -2390,9 +2395,10 @@ defmodule AshPostgres.DataLayer do identities = Ash.Resource.Info.identities(resource) table = AshPostgres.DataLayer.Info.table(resource) - identity = Enum.find(identities, fn identity -> - "#{table}_#{identity.name}_index" == constraint - end) + identity = + Enum.find(identities, fn identity -> + "#{table}_#{identity.name}_index" == constraint + end) field_names = if identity, do: identity.field_names, else: [field] diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 70f9b11d..94d5e5ec 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -563,7 +563,7 @@ defmodule AshPostgres.CalculationTest do assert [%{first_name: "abc"}, %{first_name: "tom"}] = Author - |> Ash.Query.sort(param_full_name: [separator: "~"]) + |> Ash.Query.sort(param_full_name: %{separator: "~"}) |> Ash.read!() end From 3eaa959619ea047ca4bfc58a1a308d93bb3aaa42 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 15:00:27 -0500 Subject: [PATCH 0903/1215] chore: update deps & tests --- mix.lock | 8 ++++---- test/unique_identity_test.exs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index f483186e..f96a76a5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.63", "7bf24d7e40039bf82652975460dca55e3d23fdb77738ebf665c16b7a4519b86e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.8 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "10175209ca1f01237959ce1ba0638b04c8d5a231dc0c128b2eea431ee32699e8"}, - "ash_sql": {:hex, :ash_sql, "0.2.56", "3d438a18af5d7c39e0b2b2ba6b6adc8b2abe40d9caa18af720433fc3126efac1", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "22296a2afe1f69ec1c2de7490f6821a8551c16c914e746663458068a7808a9d4"}, + "ash": {:hex, :ash, "3.4.64", "cbc337173fada2c094aa7f852fbb82d16f7090c06272aa34feb7479d1ff91162", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f29472b64cec1c340a3f2f32ef3542b4d719a041a86678d0793442922f365709"}, + "ash_sql": {:hex, :ash_sql, "0.2.57", "51a574fed322e0e6fd743362cbea264275fd5799278288a3a41aa9a2e457d56d", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0b906cef68aceb2c9666a52d4e0350c271de55483d846bed2312c59f51af2f3a"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.22", "af4deba45c9c0ffdff257f9730fcfb6322e785b030b4982550affe0aaabfa3fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "601b639bbf3a4740335a91b038999c3381ff9371f07122d0640b820cd2d5cfe3"}, + "igniter": {:hex, :igniter, "0.5.25", "a9e26794efe4b5619edd112b2ce8ffa3931f1e4d558dfebcd344553024e359b5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d944d3ed8439bb2d98391f39b86305d109f4123c947061db54c1c0f9ecad890e"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,7 +39,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.13.0", "072570a78e4cafcb81b81f143fc37f534ab7b206b2c5b445480638468bf59637", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "72aadf43584bab94b1907bff9371ac324acebd28f095f4320dd14dee2b499319"}, + "reactor": {:hex, :reactor, "0.13.2", "8260c8c7159748891298b05527b754d31c8d305a00497d03e638ba84654b8797", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ec44588d61763da544382942c0218d7f7eab454b709756c1b1b9d70ed8821a"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, diff --git a/test/unique_identity_test.exs b/test/unique_identity_test.exs index 8fae1b98..47638025 100644 --- a/test/unique_identity_test.exs +++ b/test/unique_identity_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.Test.UniqueIdentityTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.Post alias AshPostgres.Test.Organization + alias AshPostgres.Test.Post require Ash.Query From f66c743a988a1f091f3028d135c534a21cc65083 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 15:00:43 -0500 Subject: [PATCH 0904/1215] chore: release version v2.5.4 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f756d9..af71c364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.4](https://github.com/ash-project/ash_postgres/compare/v2.5.3...v2.5.4) (2025-02-17) + + + + +### Improvements: + +* Add support for field names in idenitity constraints (#478) + ## [v2.5.3](https://github.com/ash-project/ash_postgres/compare/v2.5.2...v2.5.3) (2025-02-14) diff --git a/mix.exs b/mix.exs index fe528021..c5f4943d 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.3" + @version "2.5.4" def project do [ From 72170bda79881a07ec5a5dda97e994ad5e7add7c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 17:26:51 -0500 Subject: [PATCH 0905/1215] fix: ensure field names defaults to the field of the constraint --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index dfa561c0..5fbcaa71 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2400,7 +2400,7 @@ defmodule AshPostgres.DataLayer do "#{table}_#{identity.name}_index" == constraint end) - field_names = if identity, do: identity.field_names, else: [field] + field_names = if identity, do: identity.field_names || [field], else: [field] Enum.map(field_names, fn field_name -> Ash.Error.Changes.InvalidAttribute.exception( From aafb473588e6f09bb04df4cb8ed636c23df6f866 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 18:08:31 -0500 Subject: [PATCH 0906/1215] chore: release version v2.5.5 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af71c364..f1da8e75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.5](https://github.com/ash-project/ash_postgres/compare/v2.5.4...v2.5.5) (2025-02-17) + + + + +### Bug Fixes: + +* ensure field names defaults to the field of the constraint + ## [v2.5.4](https://github.com/ash-project/ash_postgres/compare/v2.5.3...v2.5.4) (2025-02-17) diff --git a/mix.exs b/mix.exs index c5f4943d..c7463e23 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.4" + @version "2.5.5" def project do [ From a0a1c35451c06cce79cbf8d25d48cee63d2eafd3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 21:05:23 -0500 Subject: [PATCH 0907/1215] fix: don't rewrite identities when only global? is changed fix: don't modify an attribute when it only needs to be renamed --- .../migration_generator.ex | 27 +++-- test/migration_generator_test.exs | 102 ++++++++++++++++++ 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index e0301956..0ad2bc7f 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2305,21 +2305,22 @@ defmodule AshPostgres.MigrationGenerator do Enum.find( old_snapshot.attributes, fn old_attribute -> - source_match = - Enum.find_value(attributes_to_rename, old_attribute.source, fn {new, old} -> + renaming_to_source = + Enum.find_value(attributes_to_rename, fn {new, old} -> if old.source == old_attribute.source do new.source end end) - source_match == + (renaming_to_source || old_attribute.source) == attribute.source && attributes_unequal?( old_attribute, attribute, snapshot.repo, old_snapshot, - snapshot + snapshot, + not is_nil(renaming_to_source) ) end )} @@ -2522,11 +2523,25 @@ defmodule AshPostgres.MigrationGenerator do # 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) do + defp attributes_unequal?(left, right, repo, _old_snapshot, _new_snapshot, ignore_names?) do left = clean_for_equality(left, repo) right = clean_for_equality(right, repo) + left = + if ignore_names? do + Map.drop(left, [:source, :name]) + else + left + end + + right = + if ignore_names? do + Map.drop(left, [:source, :name]) + else + right + end + left != right end @@ -2583,7 +2598,7 @@ defmodule AshPostgres.MigrationGenerator do end def changing_multitenancy_affects_identities?(snapshot, old_snapshot) do - snapshot.multitenancy != old_snapshot.multitenancy || + Map.delete(snapshot.multitenancy, :global) != Map.delete(old_snapshot.multitenancy, :global) || snapshot.base_filter != old_snapshot.base_filter end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index b7f10688..c39a1578 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -584,6 +584,77 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "changing global multitenancy" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + + defposts do + identities do + identity(:title, [:title]) + end + + multitenancy do + strategy(:attribute) + attribute(:organization_id) + global?(false) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + attribute(:organization_id, :string) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + :ok + end + + test "when changing multitenancy to global, identities aren't rewritten" do + defposts do + identities do + identity(:title, [:title]) + end + + multitenancy do + strategy(:attribute) + attribute(:organization_id) + global?(true) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + attribute(:organization_id, :string) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + end + end + describe "creating follow up migrations" do setup do on_exit(fn -> @@ -614,6 +685,37 @@ defmodule AshPostgres.MigrationGeneratorTest do :ok end + test "when renaming an attribute of an index, it is properly renamed without modifying the attribute" do + defposts do + identities do + identity(:title, [:foobar]) + end + + attributes do + uuid_primary_key(:id) + attribute(:foobar, :string, public?: true) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + contents = File.read!(file2) + refute contents =~ "modify" + end + test "when renaming an index, it is properly renamed" do defposts do postgres do From 120d2049b954caed768526008ce8ddbf3b6b56b8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Feb 2025 21:22:53 -0500 Subject: [PATCH 0908/1215] chore: fix migraiton numbers --- ...e_resources49.exs => 20250217054207_migrate_resources50.exs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename priv/test_repo/migrations/{20250217054207_migrate_resources49.exs => 20250217054207_migrate_resources50.exs} (84%) diff --git a/priv/test_repo/migrations/20250217054207_migrate_resources49.exs b/priv/test_repo/migrations/20250217054207_migrate_resources50.exs similarity index 84% rename from priv/test_repo/migrations/20250217054207_migrate_resources49.exs rename to priv/test_repo/migrations/20250217054207_migrate_resources50.exs index 19f6424f..58a69c80 100644 --- a/priv/test_repo/migrations/20250217054207_migrate_resources49.exs +++ b/priv/test_repo/migrations/20250217054207_migrate_resources50.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.TestRepo.Migrations.MigrateResources49 do +defmodule AshPostgres.TestRepo.Migrations.MigrateResources50 do @moduledoc """ Updates resources based on their most recent snapshots. From 2c74df3716862b2df8c9b543856e2b9c833fd6c1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Feb 2025 22:03:00 -0500 Subject: [PATCH 0909/1215] test: capture logs in tests, test various atomic behaviors --- test/ash_postgres_test.exs | 5 +- test/support/resources/post.ex | 22 ++++++++ test/support/test_repo.ex | 2 +- test/test_helper.exs | 4 +- test/update_test.exs | 95 ++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 3 deletions(-) diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 7141b79b..a3318b57 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -5,6 +5,9 @@ defmodule AshPostgresTest do test "transaction metadata is given to on_transaction_begin" do AshPostgres.Test.Post |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.Changeset.after_action(fn _, result -> + {:ok, result} + end) |> Ash.create!() assert_receive %{ @@ -62,7 +65,7 @@ defmodule AshPostgresTest do actor: nil, actor: author ) - |> then(&AshPostgres.Test.Post.can_update_if_author?(author, &1)) + |> then(&AshPostgres.Test.Post.can_update_if_author?(author, &1, reuse_values?: true)) end) assert log == "" diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 26fae521..926e3caa 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -159,6 +159,28 @@ defmodule AshPostgres.Test.Post do change(atomic_update(:limited_score, expr((limited_score || 0) + ^arg(:amount)))) end + update :change_nothing do + accept([]) + require_atomic?(false) + change(fn changeset, _ -> changeset end) + end + + update :change_nothing_atomic do + accept([]) + require_atomic?(true) + end + + update :change_title do + accept([:title]) + require_atomic?(false) + change(fn changeset, _ -> changeset end) + end + + update :change_title_atomic do + accept([:title]) + require_atomic?(true) + end + destroy :destroy_only_freds do change(filter(expr(title == "fred"))) end diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 4b9319d1..351c94ac 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -7,7 +7,7 @@ defmodule AshPostgres.TestRepo do send(self(), data) end - def prefer_transaction?, do: true + def prefer_transaction?, do: false def prefer_transaction_for_atomic_updates?, do: false diff --git a/test/test_helper.exs b/test/test_helper.exs index 8438eaf9..056ebefd 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,6 @@ -ExUnit.start() +ExUnit.start(capture_log: true) + +Logger.configure(level: :debug) exclude_tags = case System.get_env("PG_VERSION") do diff --git a/test/update_test.exs b/test/update_test.exs index 953a55d9..ef6f1384 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -2,6 +2,8 @@ defmodule AshPostgres.UpdateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post require Ash.Query + import ExUnit.CaptureLog + import Ash.Expr test "can update with nested maps" do Post @@ -60,6 +62,99 @@ defmodule AshPostgres.UpdateTest do |> Ash.update!() end + test "timestamps arent updated if there are no changes non-atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + post2 = + post + |> Ash.update!(action: :change_nothing) + + assert post.updated_at == post2.updated_at + end + + test "no queries are run if there are no changes non-atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + assert "" = + capture_log(fn -> + post + |> Ash.update!(action: :change_nothing) + end) + end + + test "queries are run if there are no changes but there are filters non-atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + assert_raise Ash.Error.Invalid, ~r/stale/, fn -> + post + |> Ash.Changeset.for_update(:change_nothing, %{}) + |> Ash.Changeset.filter(expr(title != "match")) + |> Ash.update!(action: :change_nothing) + end + end + + test "timestamps arent updated if there are no changes atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + post2 = + post + |> Ash.update!(action: :change_nothing_atomic) + + assert post.updated_at == post2.updated_at + end + + test "timestamps arent updated if nothing changes non-atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + post2 = + post + |> Ash.update!(%{title: "match"}, action: :change_title) + + assert post.updated_at == post2.updated_at + end + + test "timestamps arent updated if nothing changes atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + post2 = + post + |> Ash.update!(%{title: "match"}, action: :change_title_atomic) + + assert post.updated_at == post2.updated_at + end + + test "queries are run if there are no changes atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + assert_raise Ash.Error.Invalid, ~r/stale/, fn -> + post + |> Ash.Changeset.for_update(:change_nothing_atomic, %{}) + |> Ash.Changeset.filter(expr(title != "match")) + |> Ash.update!(action: :change_nothing) + end + end + test "can unrelate belongs_to" do author = AshPostgres.Test.Author From 866e5a282d7902aa58621a5b50140e9a4989991a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 19 Feb 2025 09:45:52 -0500 Subject: [PATCH 0910/1215] chore: fix for recently added source renaming handling --- lib/migration_generator/migration_generator.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 0ad2bc7f..1c35b6b0 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2312,8 +2312,7 @@ defmodule AshPostgres.MigrationGenerator do end end) - (renaming_to_source || old_attribute.source) == - attribute.source && + if (renaming_to_source || old_attribute.source) == attribute.source do attributes_unequal?( old_attribute, attribute, @@ -2322,6 +2321,7 @@ defmodule AshPostgres.MigrationGenerator do snapshot, not is_nil(renaming_to_source) ) + end end )} end) @@ -2537,7 +2537,7 @@ defmodule AshPostgres.MigrationGenerator do right = if ignore_names? do - Map.drop(left, [:source, :name]) + Map.drop(right, [:source, :name]) else right end From 9b2a0192896bea6be5fc695119363c36b71fec6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Feb 2025 09:10:48 -0500 Subject: [PATCH 0911/1215] chore(deps-dev): bump the dev-dependencies group with 2 updates (#489) Bumps the dev-dependencies group with 2 updates: [ex_doc](https://github.com/elixir-lang/ex_doc) and [git_ops](https://github.com/zachdaniel/git_ops). Updates `ex_doc` from 0.37.1 to 0.37.2 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.1...v0.37.2) Updates `git_ops` from 2.6.3 to 2.7.0 - [Changelog](https://github.com/zachdaniel/git_ops/blob/master/CHANGELOG.md) - [Commits](https://github.com/zachdaniel/git_ops/compare/v2.6.3...v2.7.0) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: git_ops dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index f96a76a5..86d790a2 100644 --- a/mix.lock +++ b/mix.lock @@ -16,11 +16,11 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.37.1", "65ca30d242082b95aa852b3b73c9d9914279fff56db5dc7b3859be5504417980", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "6774f75477733ea88ce861476db031f9399c110640752ca2b400dbbb50491224"}, + "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, + "git_ops": {:hex, :git_ops, "2.7.0", "fed1400d516d06810ac46a9d4b3e12ca4973683158ddc5931935567075ff1a4c", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "29f1ddb3678969cb81dc56177d0c6e5c85a77a7ce50036207b920005cc6b5b26"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "igniter": {:hex, :igniter, "0.5.25", "a9e26794efe4b5619edd112b2ce8ffa3931f1e4d558dfebcd344553024e359b5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d944d3ed8439bb2d98391f39b86305d109f4123c947061db54c1c0f9ecad890e"}, From 5e3f12dc8d269cdd083db315b50a974bd2512b43 Mon Sep 17 00:00:00 2001 From: m0rt3nlund Date: Fri, 21 Feb 2025 15:05:37 +0100 Subject: [PATCH 0912/1215] fix: Ensure primary key migrations use prefix for multitenancy (#488) --- .gitignore | 1 + lib/migration_generator/operation.ex | 122 ++++++++++++------ .../tenants/composite_key/20250220073135.json | 68 ++++++++++ .../tenants/composite_key/20250220073141.json | 68 ++++++++++ .../20250220073135_migrate_resources5.exs | 32 +++++ .../20250220073141_migrate_resources6.exs | 29 +++++ test/migration_generator_test.exs | 85 +++++++++++- test/multitenancy_test.exs | 12 +- test/support/multitenancy/domain.ex | 1 + .../resources/composite_key_post.ex | 32 +++++ test_snapshot_path/test_repo/extensions.json | 10 -- 11 files changed, 406 insertions(+), 54 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json create mode 100644 priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json create mode 100644 priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs create mode 100644 priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs create mode 100644 test/support/multitenancy/resources/composite_key_post.ex delete mode 100644 test_snapshot_path/test_repo/extensions.json diff --git a/.gitignore b/.gitignore index 4fe6d351..7d849d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ ash_postgres-*.tar test_migration_path test_snapshots_path +test_tenant_migration_path diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 4b8c4eae..1dc72ad7 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1055,17 +1055,24 @@ defmodule AshPostgres.MigrationGenerator.Operation do @moduledoc false defstruct [:schema, :table, :keys, no_phase: true] - def up(%{schema: schema, table: table, keys: keys}) do + def up(%{schema: schema, table: table, keys: keys, multitenancy: multitenancy}) do keys = Enum.join(keys, ", ") - if schema do - """ - execute("ALTER TABLE \\\"#{schema}.#{table}\\\" ADD PRIMARY KEY (#{keys})") - """ - else - """ - execute("ALTER TABLE \\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") - """ + cond do + multitenancy.strategy == :context -> + """ + execute("ALTER TABLE \\\"\#{prefix()}\\\".\\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + + schema -> + """ + execute("ALTER TABLE \\\"#{schema}.#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + + true -> + """ + execute("ALTER TABLE \\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ end end @@ -1082,33 +1089,54 @@ defmodule AshPostgres.MigrationGenerator.Operation do "" end - def down(%{schema: schema, table: table, remove_old?: remove_old?, keys: keys}) do + def down(%{ + schema: schema, + table: table, + remove_old?: remove_old?, + keys: keys, + multitenancy: multitenancy + }) do keys = Enum.join(keys, ", ") - if schema do - remove_old = - if remove_old? do - """ - execute("ALTER TABLE \\\"#{schema}.#{table}\\\" DROP constraint #{table}_pkey") - """ - end + cond do + multitenancy.strategy == :context -> + remove_old = + if remove_old? do + """ + execute("ALTER TABLE \\\"\#{prefix()}\\\".\\\"#{table}\\\" DROP constraint #{table}_pkey") + """ + end - """ - #{remove_old} - execute("ALTER TABLE \\\"#{schema}.#{table}\\\" ADD PRIMARY KEY (#{keys})") - """ - else - remove_old = - if remove_old? do - """ - execute("ALTER TABLE \\\"#{table}\\\" DROP constraint #{table}_pkey") - """ - end + """ + #{remove_old} + execute("ALTER TABLE \\\"\#{prefix()}\\\".\\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + + not is_nil(schema) -> + remove_old = + if remove_old? do + """ + execute("ALTER TABLE \\\"#{schema}.#{table}\\\" DROP constraint #{table}_pkey") + """ + end + + """ + #{remove_old} + execute("ALTER TABLE \\\"#{schema}.#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ + + true -> + remove_old = + if remove_old? do + """ + execute("ALTER TABLE \\\"#{table}\\\" DROP constraint #{table}_pkey") + """ + end - """ - #{remove_old} - execute("ALTER TABLE \\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") - """ + """ + #{remove_old} + execute("ALTER TABLE \\\"#{table}\\\" ADD PRIMARY KEY (#{keys})") + """ end end end @@ -1117,11 +1145,16 @@ defmodule AshPostgres.MigrationGenerator.Operation do @moduledoc false defstruct [:schema, :table, no_phase: true] - def up(%{schema: schema, table: table}) do - if schema do - "drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: \"#{schema}\")" - else - "drop constraint(#{inspect(table)}, \"#{table}_pkey\")" + def up(%{schema: schema, table: table, multitenancy: multitenancy}) do + cond do + multitenancy.strategy == :context -> + "drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: prefix())" + + schema -> + "drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: \"#{schema}\")" + + true -> + "drop constraint(#{inspect(table)}, \"#{table}_pkey\")" end end @@ -1138,7 +1171,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do "" end - def down(%{schema: schema, table: table, commented?: commented?}) do + def down(%{schema: schema, table: table, commented?: commented?, multitenancy: multitenancy}) do comment = if commented? do """ @@ -1149,10 +1182,15 @@ defmodule AshPostgres.MigrationGenerator.Operation do "" end - if schema do - "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: \"#{schema}\")" - else - "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\")" + cond do + multitenancy.strategy == :context -> + "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: prefix())" + + schema -> + "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: \"#{schema}\")" + + true -> + "#{comment}drop constraint(#{inspect(table)}, \"#{table}_pkey\")" end end end diff --git a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json new file mode 100644 index 00000000..b2b0cb0c --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json @@ -0,0 +1,68 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": true, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "name": "composite_key_org_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_orgs" + }, + "size": null, + "source": "org_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "163B8B70E51926917188C902BA9E759A56F2C295D84FAA2AC02BF4F602139FA3", + "identities": [], + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "composite_key" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json new file mode 100644 index 00000000..253fe8fb --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json @@ -0,0 +1,68 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": true, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "title", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "name": "composite_key_org_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_orgs" + }, + "size": null, + "source": "org_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "4A793B2AE363407E93E81FA4CABF458C1F403432634A400B84ADDD7486D986BD", + "identities": [], + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "composite_key" +} \ No newline at end of file diff --git a/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs b/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs new file mode 100644 index 00000000..923ba511 --- /dev/null +++ b/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs @@ -0,0 +1,32 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources5 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 + create table(:composite_key, primary_key: false, prefix: prefix()) do + add(:id, :bigserial, null: false, primary_key: true) + add(:title, :text, null: false) + + add( + :org_id, + references(:multitenant_orgs, + column: :id, + prefix: "public", + name: "composite_key_org_id_fkey", + type: :uuid + ) + ) + end + end + + def down do + drop(constraint(:composite_key, "composite_key_org_id_fkey")) + + drop(table(:composite_key, prefix: prefix())) + end +end diff --git a/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs b/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs new file mode 100644 index 00000000..f5018509 --- /dev/null +++ b/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs @@ -0,0 +1,29 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources6 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 + drop(constraint("composite_key", "composite_key_pkey", prefix: prefix())) + + alter table(:composite_key, prefix: prefix()) do + modify(:title, :text) + end + + execute("ALTER TABLE \"#{prefix()}\".\"composite_key\" ADD PRIMARY KEY (id, title)") + end + + def down do + drop(constraint("composite_key", "composite_key_pkey", prefix: prefix())) + + alter table(:composite_key, prefix: prefix()) do + modify(:title, :text) + end + + execute("ALTER TABLE \"#{prefix()}\".\"composite_key\" ADD PRIMARY KEY (id)") + end +end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index c39a1578..88f76f67 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -492,6 +492,89 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "creating a multitenancy resource without composite key, adding it later" do + setup do + on_exit(fn -> + nil + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + File.rm_rf!("test_tenant_migration_path") + end) + + :ok + end + + test "create without composite key, then add extra key" do + defposts do + postgres do + schema("example") + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true, allow_nil?: false) + end + + multitenancy do + strategy(:context) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + tenant_migration_path: "test_tenant_migration_path", + quiet: false, + format: false + ) + + defposts do + postgres do + schema("example") + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true, primary_key?: true, allow_nil?: false) + end + + multitenancy do + strategy(:context) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + tenant_migration_path: "test_tenant_migration_path", + quiet: false, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_tenant_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + contents = File.read!(file2) + + [up_side, down_side] = String.split(contents, "def down", parts: 2) + + assert up_side =~ + ~S[execute("ALTER TABLE \"#{prefix()}\".\"posts\" ADD PRIMARY KEY (id, title)")] + + assert down_side =~ + ~S[execute("ALTER TABLE \"#{prefix()}\".\"posts\" ADD PRIMARY KEY (id)")] + end + end + describe "creating follow up migrations with a schema" do setup do on_exit(fn -> @@ -1156,7 +1239,7 @@ defmodule AshPostgres.MigrationGeneratorTest do test "returns code(1) if snapshots and resources don't fit", %{domain: domain} do assert catch_exit( AshPostgres.MigrationGenerator.generate(domain, - snapshot_path: "test_snapshot_path", + snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", check: true ) diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index afd5cbe5..73f9cb52 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -2,7 +2,7 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false require Ash.Query - alias AshPostgres.MultitenancyTest.{Org, Post, User} + alias AshPostgres.MultitenancyTest.{CompositeKeyPost, Org, Post, User} alias AshPostgres.Test.Post, as: GlobalPost setup do @@ -125,6 +125,16 @@ defmodule AshPostgres.Test.MultitenancyTest do ) end + test "composite key multitenancy works", %{org1: org1} do + CompositeKeyPost + |> Ash.Changeset.for_create(:create, %{title: "foo"}) + |> Ash.Changeset.manage_relationship(:org, org1, type: :append_and_remove) + |> Ash.Changeset.set_tenant(org1) + |> Ash.create!() + + assert [_] = CompositeKeyPost |> Ash.Query.set_tenant(org1) |> Ash.read!() + end + test "loading attribute multitenant resources from context multitenant resources works" do org = Org diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 2394c234..85f078da 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -9,6 +9,7 @@ defmodule AshPostgres.MultitenancyTest.Domain do resource(AshPostgres.MultitenancyTest.PostLink) resource(AshPostgres.MultitenancyTest.NonMultitenantPostLink) resource(AshPostgres.MultitenancyTest.CrossTenantPostLink) + resource(AshPostgres.MultitenancyTest.CompositeKeyPost) end authorization do diff --git a/test/support/multitenancy/resources/composite_key_post.ex b/test/support/multitenancy/resources/composite_key_post.ex new file mode 100644 index 00000000..84f7881a --- /dev/null +++ b/test/support/multitenancy/resources/composite_key_post.ex @@ -0,0 +1,32 @@ +defmodule AshPostgres.MultitenancyTest.CompositeKeyPost do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "composite_key" + repo AshPostgres.TestRepo + end + + multitenancy do + strategy(:context) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + attributes do + integer_primary_key(:id) + attribute(:title, :string, public?: true, allow_nil?: false, primary_key?: true) + end + + relationships do + belongs_to(:org, AshPostgres.MultitenancyTest.Org) do + public?(true) + end + end +end diff --git a/test_snapshot_path/test_repo/extensions.json b/test_snapshot_path/test_repo/extensions.json deleted file mode 100644 index e084bbff..00000000 --- a/test_snapshot_path/test_repo/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "installed": [ - "ash-functions", - "uuid-ossp", - "pg_trgm", - "citext", - "demo-functions_v1" - ], - "ash_functions_version": 3 -} \ No newline at end of file From 562caa2eec60ecfc6ec258bd884bb4c591151635 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Feb 2025 09:07:19 -0500 Subject: [PATCH 0913/1215] test: add test for ash_sql aggregate fix --- test/aggregate_test.exs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 14ff389b..6838a0b1 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -16,6 +16,10 @@ defmodule AshSql.AggregateTest do Organization |> Ash.read!(load: [:no_cast_open_posts_count]) end + test "count aggregate on resource with no primary key with no field specified" do + assert Ash.count!(AshPostgres.Test.PostView) == 0 + end + test "relates to actor via has_many and with an aggregate" do org = Organization From 88035fe5c125b38f762238bdfaa09eae1bff4075 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Feb 2025 09:09:03 -0500 Subject: [PATCH 0914/1215] chore: update deps --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 86d790a2..65d1af6b 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.0", "fed1400d516d06810ac46a9d4b3e12ca4973683158ddc5931935567075ff1a4c", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "29f1ddb3678969cb81dc56177d0c6e5c85a77a7ce50036207b920005cc6b5b26"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.25", "a9e26794efe4b5619edd112b2ce8ffa3931f1e4d558dfebcd344553024e359b5", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d944d3ed8439bb2d98391f39b86305d109f4123c947061db54c1c0f9ecad890e"}, + "igniter": {:hex, :igniter, "0.5.27", "7c633dd99150e9cad68285ec8ad7e15833ff0c72d46774ed3be7728c661ec4cb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3042a71d4466e9c9b98a23d182eb02014a1c4802a35de0fa8233263d27c99550"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,7 +39,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.13.2", "8260c8c7159748891298b05527b754d31c8d305a00497d03e638ba84654b8797", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ec44588d61763da544382942c0218d7f7eab454b709756c1b1b9d70ed8821a"}, + "reactor": {:hex, :reactor, "0.13.3", "8d49362564970c3331ba306213bc2416c682a04bfab0f710ac3c740060bbdc71", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b8227ed82a2aabaedc24a09e347002bb14c58701989d7383c51e941e03085180"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, From 82dd4d0346e2c7e325867d9bd8c491e0947c77dd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 24 Feb 2025 12:15:38 -0500 Subject: [PATCH 0915/1215] fix: start lateral join source query bindings at 500 --- lib/data_layer.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5fbcaa71..82142e0c 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1296,6 +1296,9 @@ defmodule AshPostgres.DataLayer do true ) }) + # This is a hack, but surely there is just no way someone writes + # a 500 binding query as the base of a lateral join query... + |> Ash.Query.set_context(%{data_layer: %{start_bindings_at: 500}}) |> Ash.Query.set_tenant(source_query.tenant) |> filter_for_records(root_data) |> set_lateral_join_prefix(query) From 5004faa6fb0775ef4e55505cc43d2f9a2b3ff1eb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 24 Feb 2025 12:25:52 -0500 Subject: [PATCH 0916/1215] improvement: support SKIP LOCKED in locks --- lib/data_layer.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 82142e0c..17fd1110 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3214,6 +3214,13 @@ defmodule AshPostgres.DataLayer do def lock(query, unquote(lock), _) do {:ok, Ecto.Query.lock(query, [{^0, a}], fragment(unquote(frag), a))} end + + frag = "#{lock} OF ? SKIP LOCKED" + lock = "#{lock} SKIP LOCKED" + + def lock(query, unquote(lock), _) do + {:ok, Ecto.Query.lock(query, [{^0, a}], fragment(unquote(frag), a))} + end end @impl true From 89326d442377a6bf14ae35e597cf33c9c8989f43 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 24 Feb 2025 20:54:01 -0500 Subject: [PATCH 0917/1215] chore: update for ash_sql/ash compatibility --- lib/data_layer.ex | 38 +++++++++++++++++++++++++++++--------- test/load_test.exs | 7 +++++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 17fd1110..b66cdfc7 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1159,10 +1159,12 @@ defmodule AshPostgres.DataLayer do {:ok, data_layer_query} -> data_layer_query = Ecto.Query.exclude(data_layer_query, :select) + through_binding = Map.get(query, :__ash_bindings__)[:current] + through_resource |> Ash.Query.new() |> Ash.Query.put_context(:data_layer, %{ - start_bindings_at: Map.get(data_layer_query, :__ash_bindings__)[:current] + start_bindings_at: through_binding }) |> Ash.Query.set_context(through_relationship.context) |> Ash.Query.do_filter(through_relationship.filter) @@ -1192,7 +1194,7 @@ defmodule AshPostgres.DataLayer do source_query, relationship.through ), - as: ^Map.get(data_layer_query, :__ash_bindings__)[:current], + as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == field(destination, ^destination_attribute), @@ -1230,7 +1232,7 @@ defmodule AshPostgres.DataLayer do source_query, relationship.through ), - as: ^Map.get(data_layer_query, :__ash_bindings__)[:current], + as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == field(destination, ^destination_attribute), @@ -1295,10 +1297,8 @@ defmodule AshPostgres.DataLayer do :no_inner_join?, true ) + |> Map.delete(:lateral_join_source) }) - # This is a hack, but surely there is just no way someone writes - # a 500 binding query as the base of a lateral join query... - |> Ash.Query.set_context(%{data_layer: %{start_bindings_at: 500}}) |> Ash.Query.set_tenant(source_query.tenant) |> filter_for_records(root_data) |> set_lateral_join_prefix(query) @@ -3259,12 +3259,20 @@ defmodule AshPostgres.DataLayer do def filter(query, filter, resource, opts \\ []) do used_aggregates = Ash.Filter.used_aggregates(filter, []) + query = + AshSql.Bindings.default_bindings(query, resource, AshPostgres.SqlImplementation) + query |> AshSql.Join.join_all_relationships(filter, opts) |> case do {:ok, query} -> query - |> AshSql.Aggregate.add_aggregates(used_aggregates, resource, false, 0) + |> AshSql.Aggregate.add_aggregates( + used_aggregates, + resource, + false, + query.__ash_bindings__.root_binding + ) |> case do {:ok, query} -> {:ok, AshSql.Filter.add_filter_expression(query, filter)} @@ -3280,12 +3288,24 @@ defmodule AshPostgres.DataLayer do @impl true def add_aggregates(query, aggregates, resource) do - AshSql.Aggregate.add_aggregates(query, aggregates, resource, true, 0) + AshSql.Aggregate.add_aggregates( + query, + aggregates, + resource, + true, + query.__ash_bindings__.root_binding + ) end @impl true def add_calculations(query, calculations, resource, select? \\ true) do - AshSql.Calculation.add_calculations(query, calculations, resource, 0, select?) + AshSql.Calculation.add_calculations( + query, + calculations, + resource, + query.__ash_bindings__.root_binding, + select? + ) end def add_known_binding(query, data, known_binding) do diff --git a/test/load_test.exs b/test/load_test.exs index 9261e084..63b142cd 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -990,8 +990,11 @@ defmodule AshPostgres.Test.LoadTest do |> Ash.Query.load(posts: paginated_posts) |> Ash.read!(page: [limit: 1, count: true]) - assert %Ash.Page.Offset{count: 5, results: [%{followers: %Ash.Page.Offset{count: 3}}]} = - author1.posts + assert %Ash.Page.Offset{} = page = author1.posts + assert [result] = page.results + assert %Ash.Page.Offset{} = nested_page = result.followers + assert length(nested_page.results) == 1 + assert nested_page.count == 3 end test "doesn't leak the internal count aggregate when counting" do From 545de455f098a2d0c671e3a4514bbab90c2b0afd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Feb 2025 15:16:59 -0500 Subject: [PATCH 0918/1215] chore: update ash & ash_sql --- mix.exs | 4 ++-- mix.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index c7463e23..f29966ca 100644 --- a/mix.exs +++ b/mix.exs @@ -165,8 +165,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.64")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.43")}, + {:ash, ash_version("~> 3.4 and >= 3.4.65")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.57")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index 65d1af6b..7a5ba41f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.64", "cbc337173fada2c094aa7f852fbb82d16f7090c06272aa34feb7479d1ff91162", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f29472b64cec1c340a3f2f32ef3542b4d719a041a86678d0793442922f365709"}, + "ash": {:hex, :ash, "3.4.65", "798f90daee12ef9b441381f8e6b1ec0016e70933425a1ec72e60467e648435f7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "309febd8866bfdc9506a757687daf2bd54c60949cda04cf8177abc645854defe"}, "ash_sql": {:hex, :ash_sql, "0.2.57", "51a574fed322e0e6fd743362cbea264275fd5799278288a3a41aa9a2e457d56d", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0b906cef68aceb2c9666a52d4e0350c271de55483d846bed2312c59f51af2f3a"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.0", "fed1400d516d06810ac46a9d4b3e12ca4973683158ddc5931935567075ff1a4c", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "29f1ddb3678969cb81dc56177d0c6e5c85a77a7ce50036207b920005cc6b5b26"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.27", "7c633dd99150e9cad68285ec8ad7e15833ff0c72d46774ed3be7728c661ec4cb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3042a71d4466e9c9b98a23d182eb02014a1c4802a35de0fa8233263d27c99550"}, + "igniter": {:hex, :igniter, "0.5.29", "6bf7ddaf15e88ae75f6dad514329530ec8f4721ba14782f6386a7345c1be99fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "beb6e0f69fc6d4e3975ffa26c5459fc63fd96f85cfaeba984c2dfd3d7333b6ad"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From e5b0acf9744548fdf19e4b1519cb718025d9d11b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Feb 2025 15:19:42 -0500 Subject: [PATCH 0919/1215] chore: release version v2.5.6 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1da8e75..18b3864e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.6](https://github.com/ash-project/ash_postgres/compare/v2.5.5...v2.5.6) (2025-02-25) + + + + +### Bug Fixes: + +* start lateral join source query bindings at 500 + +* Ensure primary key migrations use prefix for multitenancy (#488) + +* don't rewrite identities when only global? is changed + +* don't modify an attribute when it only needs to be renamed + +### Improvements: + +* support SKIP LOCKED in locks + ## [v2.5.5](https://github.com/ash-project/ash_postgres/compare/v2.5.4...v2.5.5) (2025-02-17) diff --git a/mix.exs b/mix.exs index f29966ca..1662f41b 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.5" + @version "2.5.6" def project do [ From 66293980f6118d719b258b769aacfcedaa030d81 Mon Sep 17 00:00:00 2001 From: Peter Shoukry Date: Wed, 26 Feb 2025 01:23:11 +0200 Subject: [PATCH 0920/1215] tests: Schema multitenancy fails (#434) --- test/aggregate_test.exs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 6838a0b1..d451f549 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -65,6 +65,40 @@ defmodule AshSql.AggregateTest do assert read_post.count_of_comments == 1 end + describe "Context Multitenancy" do + alias AshPostgres.MultitenancyTest.{Org, Post, User} + + test "loading a nested aggregate honors tenant" do + alias AshPostgres.MultitenancyTest.{Org, Post, User} + + org = + Org + |> Ash.Changeset.for_create(:create, %{name: "BTTF"}) + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{name: "Marty", org_id: org.id}) + |> Ash.create!() + + ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] + |> Enum.map( + &(Post + |> Ash.Changeset.for_create(:create, %{name: &1, user_id: user.id}) + |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) + ) + + assert Ash.load!(user, :count_visited, tenant: "org_#{org.id}") + |> then(& &1.count_visited) == 4 + + assert Ash.load!(org, :total_posts) + |> then(& &1.total_posts) == 4 + + assert Ash.load!(org, :total_users_posts, tenant: "org_#{org.id}") + |> then(& &1.total_users_posts) == 4 + end + end + describe "join filters" do test "with no data, it does not effect the behavior" do Author From 01e066ecdcd59be46558138766fd980b0dda6f4b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Feb 2025 18:35:53 -0500 Subject: [PATCH 0921/1215] chore: update tests --- mix.lock | 2 +- test/aggregate_test.exs | 34 ---------------------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/mix.lock b/mix.lock index 7a5ba41f..1985058d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.65", "798f90daee12ef9b441381f8e6b1ec0016e70933425a1ec72e60467e648435f7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "309febd8866bfdc9506a757687daf2bd54c60949cda04cf8177abc645854defe"}, - "ash_sql": {:hex, :ash_sql, "0.2.57", "51a574fed322e0e6fd743362cbea264275fd5799278288a3a41aa9a2e457d56d", [:mix], [{:ash, ">= 3.4.60 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0b906cef68aceb2c9666a52d4e0350c271de55483d846bed2312c59f51af2f3a"}, + "ash_sql": {:hex, :ash_sql, "0.2.58", "99eef5b24bd61432ba3b096217d034071f44afc99343a81963960fb21ccde87f", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "21547dedfb8c265bf78e38645a12435bad765bf62d63fcf7a50a1a7eee91e549"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index d451f549..6838a0b1 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -65,40 +65,6 @@ defmodule AshSql.AggregateTest do assert read_post.count_of_comments == 1 end - describe "Context Multitenancy" do - alias AshPostgres.MultitenancyTest.{Org, Post, User} - - test "loading a nested aggregate honors tenant" do - alias AshPostgres.MultitenancyTest.{Org, Post, User} - - org = - Org - |> Ash.Changeset.for_create(:create, %{name: "BTTF"}) - |> Ash.create!() - - user = - User - |> Ash.Changeset.for_create(:create, %{name: "Marty", org_id: org.id}) - |> Ash.create!() - - ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] - |> Enum.map( - &(Post - |> Ash.Changeset.for_create(:create, %{name: &1, user_id: user.id}) - |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) - ) - - assert Ash.load!(user, :count_visited, tenant: "org_#{org.id}") - |> then(& &1.count_visited) == 4 - - assert Ash.load!(org, :total_posts) - |> then(& &1.total_posts) == 4 - - assert Ash.load!(org, :total_users_posts, tenant: "org_#{org.id}") - |> then(& &1.total_users_posts) == 4 - end - end - describe "join filters" do test "with no data, it does not effect the behavior" do Author From 4202334809ee01af289d374907fb42a96f1454a1 Mon Sep 17 00:00:00 2001 From: capoccias <156086649+capoccias@users.noreply.github.com> Date: Thu, 27 Feb 2025 05:07:12 +1030 Subject: [PATCH 0922/1215] fix: don't rely on private function from `Ecto.Repo` (#492) --- lib/repo.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/repo.ex b/lib/repo.ex index a0bb292d..02d8c703 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -182,6 +182,17 @@ defmodule AshPostgres.Repo do def on_transaction_begin(_reason), do: :ok + # copied from Ecto.Repo + defp ash_postgres_prepare_opts(operation_name, []), + do: default_options(operation_name) + + defp ash_postgres_prepare_opts(operation_name, [{key, _} | _rest] = opts) + when is_atom(key) do + operation_name + |> default_options() + |> Keyword.merge(opts) + end + def insert(struct_or_changeset, opts \\ []) do struct_or_changeset |> to_ecto() @@ -192,7 +203,7 @@ defmodule AshPostgres.Repo do __MODULE__, repo, value, - Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:insert, opts)) + Ecto.Repo.Supervisor.tuplet(repo, ash_postgres_prepare_opts(:insert, opts)) ) end) |> from_ecto() @@ -208,7 +219,7 @@ defmodule AshPostgres.Repo do __MODULE__, repo, value, - Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:insert, opts)) + Ecto.Repo.Supervisor.tuplet(repo, ash_postgres_prepare_opts(:insert, opts)) ) end) |> from_ecto() From 9774a81d9ff0e8394b38e27f364386840cc7188d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:46:58 -0500 Subject: [PATCH 0923/1215] chore(deps): bump ash_sql in the production-dependencies group (#493) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.58 to 0.2.59 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.58...v0.2.59) --- updated-dependencies: - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 1985058d..7d624634 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.4.65", "798f90daee12ef9b441381f8e6b1ec0016e70933425a1ec72e60467e648435f7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "309febd8866bfdc9506a757687daf2bd54c60949cda04cf8177abc645854defe"}, - "ash_sql": {:hex, :ash_sql, "0.2.58", "99eef5b24bd61432ba3b096217d034071f44afc99343a81963960fb21ccde87f", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "21547dedfb8c265bf78e38645a12435bad765bf62d63fcf7a50a1a7eee91e549"}, + "ash_sql": {:hex, :ash_sql, "0.2.59", "86c04d6220772bbb6f48bd93f61afde914467daba81a301f9f77489fae1c9a53", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5e98bfdfb601805bbc83326bea6b56dfa8dcdf934f355c1c1753b60c33d6508d"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From 97e7a059ec601a842c78790696b83962e3128412 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:47:08 -0500 Subject: [PATCH 0924/1215] chore(deps-dev): bump git_ops in the dev-dependencies group (#494) Bumps the dev-dependencies group with 1 update: [git_ops](https://github.com/zachdaniel/git_ops). Updates `git_ops` from 2.7.0 to 2.7.1 - [Changelog](https://github.com/zachdaniel/git_ops/blob/master/CHANGELOG.md) - [Commits](https://github.com/zachdaniel/git_ops/compare/v2.7.0...v2.7.1) --- updated-dependencies: - dependency-name: git_ops dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 7d624634..8c25021d 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.7.0", "fed1400d516d06810ac46a9d4b3e12ca4973683158ddc5931935567075ff1a4c", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "29f1ddb3678969cb81dc56177d0c6e5c85a77a7ce50036207b920005cc6b5b26"}, + "git_ops": {:hex, :git_ops, "2.7.1", "127a0a28925372472c37b5e012f872c3edb95ead2824682b5f6bd91180600772", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "006c6284a08129cc36ba5073dc1337917fa88e85ed98c409c8cc665db9c6a5d4"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "igniter": {:hex, :igniter, "0.5.29", "6bf7ddaf15e88ae75f6dad514329530ec8f4721ba14782f6386a7345c1be99fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "beb6e0f69fc6d4e3975ffa26c5459fc63fd96f85cfaeba984c2dfd3d7333b6ad"}, From f70cd7016cbda5794f21c894f8879b5f43c745ef Mon Sep 17 00:00:00 2001 From: Peter Shoukry Date: Thu, 27 Feb 2025 19:13:08 +0200 Subject: [PATCH 0925/1215] test: schema multitenant aggregates wrong tenant (#491) --- test/aggregate_test.exs | 34 +++++++++++++++++++++ test/support/multitenancy/resources/org.ex | 5 +++ test/support/multitenancy/resources/user.ex | 1 + 3 files changed, 40 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 6838a0b1..d451f549 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -65,6 +65,40 @@ defmodule AshSql.AggregateTest do assert read_post.count_of_comments == 1 end + describe "Context Multitenancy" do + alias AshPostgres.MultitenancyTest.{Org, Post, User} + + test "loading a nested aggregate honors tenant" do + alias AshPostgres.MultitenancyTest.{Org, Post, User} + + org = + Org + |> Ash.Changeset.for_create(:create, %{name: "BTTF"}) + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{name: "Marty", org_id: org.id}) + |> Ash.create!() + + ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] + |> Enum.map( + &(Post + |> Ash.Changeset.for_create(:create, %{name: &1, user_id: user.id}) + |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) + ) + + assert Ash.load!(user, :count_visited, tenant: "org_#{org.id}") + |> then(& &1.count_visited) == 4 + + assert Ash.load!(org, :total_posts) + |> then(& &1.total_posts) == 4 + + assert Ash.load!(org, :total_users_posts, tenant: "org_#{org.id}") + |> then(& &1.total_users_posts) == 4 + end + end + describe "join filters" do test "with no data, it does not effect the behavior" do Author diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index 15c24e05..aa68daf4 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -59,6 +59,11 @@ defmodule AshPostgres.MultitenancyTest.Org do parse_attribute({__MODULE__, :tenant, []}) end + aggregates do + count(:total_users_posts, [:users, :posts]) + count(:total_posts, :posts) + end + relationships do belongs_to :owner, AshPostgres.MultitenancyTest.User do attribute_public?(false) diff --git a/test/support/multitenancy/resources/user.ex b/test/support/multitenancy/resources/user.ex index d164c28b..77149182 100644 --- a/test/support/multitenancy/resources/user.ex +++ b/test/support/multitenancy/resources/user.ex @@ -42,6 +42,7 @@ defmodule AshPostgres.MultitenancyTest.User do aggregates do list(:years_visited, :posts, :last_word) + count(:count_visited, :posts) end def parse_tenant("org_" <> id), do: id From 55d9b9553795f73394af7c6dfe28203c6db4ae54 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 27 Feb 2025 13:27:57 -0500 Subject: [PATCH 0926/1215] test: add tenant in aggregate test --- test/aggregate_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index d451f549..567edd10 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -91,8 +91,8 @@ defmodule AshSql.AggregateTest do assert Ash.load!(user, :count_visited, tenant: "org_#{org.id}") |> then(& &1.count_visited) == 4 - assert Ash.load!(org, :total_posts) - |> then(& &1.total_posts) == 4 + assert Ash.load!(org, :total_posts, tenant: "org_#{org.id}") + |> then(& &1.total_posts) == 0 assert Ash.load!(org, :total_users_posts, tenant: "org_#{org.id}") |> then(& &1.total_users_posts) == 4 From 8bfbcefd2a0796f0fa9ec683026e742d57a2cb1d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 28 Feb 2025 18:22:42 -0500 Subject: [PATCH 0927/1215] fix: check for stale record errors on destroy fixes #1831 --- lib/data_layer.ex | 13 +++++++++++-- test/destroy_test.exs | 25 +++++++++++++++++++++++++ test/support/resources/post.ex | 4 ++++ test/update_test.exs | 20 +++++++++++++++++--- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b66cdfc7..38ceb56a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3170,9 +3170,18 @@ defmodule AshPostgres.DataLayer do query, repo_opts ) - end) + |> case do + {0, _} -> + {:error, + Ash.Error.Changes.StaleRecord.exception( + resource: resource, + filter: changeset.filter + )} - :ok + _ -> + :ok + end + end) rescue e -> handle_raised_error(e, __STACKTRACE__, ecto_changeset, resource) diff --git a/test/destroy_test.exs b/test/destroy_test.exs index 87ed72d0..974f2cb3 100644 --- a/test/destroy_test.exs +++ b/test/destroy_test.exs @@ -40,4 +40,29 @@ defmodule AshPostgres.DestroyTest do |> Ash.destroy!() end end + + test "can optimistic lock" do + post = + Post + |> Ash.Changeset.for_create(:create, %{}) + |> Ash.create!() + + post + |> Ash.Changeset.for_update( + :optimistic_lock, + %{ + title: "george" + } + ) + |> Ash.update!() + + assert_raise Ash.Error.Invalid, ~r/Attempted to update stale record/, fn -> + post + |> Ash.Changeset.for_destroy( + :optimistic_lock_destroy, + %{} + ) + |> Ash.destroy!() + end + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 926e3caa..93a4e0d3 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -396,6 +396,10 @@ defmodule AshPostgres.Test.Post do change(optimistic_lock(:version)) end + destroy :optimistic_lock_destroy do + change(optimistic_lock(:version)) + end + read :read_with_related_list_agg_filter do pagination(keyset?: true, default_limit: 25) filter(expr(count_nils(latest_comment.linked_comment_post_ids) == 0)) diff --git a/test/update_test.exs b/test/update_test.exs index ef6f1384..cf93b0f1 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -50,9 +50,12 @@ defmodule AshPostgres.UpdateTest do } ) - Post - |> Ash.Changeset.for_create(:create, %{title: "fred"}) - |> Ash.create!() + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create!() + + post |> Ash.Changeset.for_update( :optimistic_lock, %{ @@ -60,6 +63,17 @@ defmodule AshPostgres.UpdateTest do } ) |> Ash.update!() + + assert_raise Ash.Error.Invalid, ~r/Attempted to update stale record/, fn -> + post + |> Ash.Changeset.for_update( + :optimistic_lock, + %{ + title: "harry" + } + ) + |> Ash.update!() + end end test "timestamps arent updated if there are no changes non-atomically" do From 1ec8d822a3bd65401f374525a1694ab06106e35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Sun, 2 Mar 2025 23:04:36 +0100 Subject: [PATCH 0928/1215] fix: Use exclusion_constraint instead of check_constraint in add_exclusion_constraints (#495) --- lib/data_layer.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 38ceb56a..c0eb1cdd 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2555,13 +2555,13 @@ defmodule AshPostgres.DataLayer do {key, name} -> case repo.default_constraint_match_type(:check, name) do {:regex, regex} -> - Ecto.Changeset.check_constraint(changeset, key, + Ecto.Changeset.exclusion_constraint(changeset, key, name: regex, match: :exact ) match -> - Ecto.Changeset.check_constraint(changeset, key, + Ecto.Changeset.exclusion_constraint(changeset, key, name: name, match: match ) @@ -2570,14 +2570,14 @@ defmodule AshPostgres.DataLayer do {key, name, message} -> case repo.default_constraint_match_type(:check, name) do {:regex, regex} -> - Ecto.Changeset.check_constraint(changeset, key, + Ecto.Changeset.exclusion_constraint(changeset, key, name: regex, message: message, match: :exact ) match -> - Ecto.Changeset.check_constraint(changeset, key, + Ecto.Changeset.exclusion_constraint(changeset, key, name: name, message: message, match: match From 20a8631ab759fe659d3296b697930c217b31c9a1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 3 Mar 2025 17:54:49 -0500 Subject: [PATCH 0929/1215] test: add tests for fix in `ash_sql` --- test/aggregate_test.exs | 29 +++++++++++++++++++++++++++++ test/support/resources/post.ex | 7 +++++++ 2 files changed, 36 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 567edd10..0bb25e81 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -65,6 +65,35 @@ defmodule AshSql.AggregateTest do assert read_post.count_of_comments == 1 end + test "nested filters on aggregates works" do + org = + Organization + |> Ash.Changeset.for_create(:create, %{name: "match"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) + |> Ash.create!() + + assert [%{count_of_comments_matching_org_name: 1}] = + Post + |> Ash.Query.load(:count_of_comments_matching_org_name) + |> Ash.Query.filter(id == ^post.id) + |> Ash.read!() + end + describe "Context Multitenancy" do alias AshPostgres.MultitenancyTest.{Org, Post, User} diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 93a4e0d3..9fb97cdd 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -850,6 +850,13 @@ defmodule AshPostgres.Test.Post do filter(title: "match") end + count :count_of_comments_matching_org_name, [ + :posts_with_matching_title, + :comments + ] do + filter(expr(parent(organization.name) == title)) + end + count(:count_of_comments_containing_title, :comments_containing_title) first :first_comment, :comments, :title do From de3724220047650f5e6b5826a926a70349ec3500 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Mon, 3 Mar 2025 22:09:39 -0600 Subject: [PATCH 0930/1215] fix: handle errors from identities in polymorphic resources properly (#497) --- lib/data_layer.ex | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c0eb1cdd..d09c88e2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1964,7 +1964,17 @@ defmodule AshPostgres.DataLayer do end rescue e -> - changeset = Ash.Changeset.new(resource) + changeset = + case source do + {table, resource} -> + resource + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:data_layer, %{table: table}) + + resource -> + resource + |> Ash.Changeset.new() + end handle_raised_error( e, From 9a047a23a98e2649527b88fb897ffa56f09be0e0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 4 Mar 2025 10:52:13 -0500 Subject: [PATCH 0931/1215] chore: release version v2.5.7 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b3864e..fc401cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.7](https://github.com/ash-project/ash_postgres/compare/v2.5.6...v2.5.7) (2025-03-04) + + + + +### Bug Fixes: + +* handle errors from identities in polymorphic resources properly (#497) + +* Use exclusion_constraint instead of check_constraint in add_exclusion_constraints (#495) + +* check for stale record errors on destroy + +* don't rely on private function from `Ecto.Repo` (#492) + ## [v2.5.6](https://github.com/ash-project/ash_postgres/compare/v2.5.5...v2.5.6) (2025-02-25) diff --git a/mix.exs b/mix.exs index 1662f41b..0cb046cd 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.6" + @version "2.5.7" def project do [ From b368150aceff8bb5d2f24f100a2cbd147640fc67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Mar 2025 07:30:50 -0800 Subject: [PATCH 0932/1215] chore(deps): bump the production-dependencies group with 3 updates (#499) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.65 to 3.4.67 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.65...v3.4.67) Updates `ash_sql` from 0.2.59 to 0.2.60 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.59...v0.2.60) Updates `igniter` from 0.5.29 to 0.5.31 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.29...v0.5.31) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 8c25021d..dd709088 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.65", "798f90daee12ef9b441381f8e6b1ec0016e70933425a1ec72e60467e648435f7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "309febd8866bfdc9506a757687daf2bd54c60949cda04cf8177abc645854defe"}, - "ash_sql": {:hex, :ash_sql, "0.2.59", "86c04d6220772bbb6f48bd93f61afde914467daba81a301f9f77489fae1c9a53", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5e98bfdfb601805bbc83326bea6b56dfa8dcdf934f355c1c1753b60c33d6508d"}, + "ash": {:hex, :ash, "3.4.67", "b2e3e65ddff550998a5c8dd1fc7f8ef4d7ebfe75a474c1e620e9b51b3ff5f70d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "770a637208ef0be5b9778687efb55be7b4f75d0202f80ddc4b1b918a8282ba28"}, + "ash_sql": {:hex, :ash_sql, "0.2.60", "3920e05ba34df913f4e32c7395480687a0372db7c2725b7c7c390b432e28be08", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "dcc3eb8277ccb456cd9ff1e4d9b06699ce9f0502c1dfefee388eb1850817e77e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.1", "127a0a28925372472c37b5e012f872c3edb95ead2824682b5f6bd91180600772", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "006c6284a08129cc36ba5073dc1337917fa88e85ed98c409c8cc665db9c6a5d4"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.29", "6bf7ddaf15e88ae75f6dad514329530ec8f4721ba14782f6386a7345c1be99fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "beb6e0f69fc6d4e3975ffa26c5459fc63fd96f85cfaeba984c2dfd3d7333b6ad"}, + "igniter": {:hex, :igniter, "0.5.31", "efee5efb94bcfce37d317ab54c54b979d8864bf9b76aafded82f7bb52a306076", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4f0c9456135444286ae89b22db10f8f1ad18932ccd4935bc14a5ad41704e5a1f"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,7 +39,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.13.3", "8d49362564970c3331ba306213bc2416c682a04bfab0f710ac3c740060bbdc71", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b8227ed82a2aabaedc24a09e347002bb14c58701989d7383c51e941e03085180"}, + "reactor": {:hex, :reactor, "0.14.0", "8dc5d4946391010bf9fa7b58dd1e75d3c1cf97315e5489b7797cf64b82ae27a4", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9cf5068e4042791c150f0dfbc00f4f435433eb948036b44b95b940e457b35a6a"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, @@ -47,7 +47,7 @@ "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, "spark": {:hex, :spark, "2.2.45", "19e3a879e80d02853ded85ed7b4c0a84a5d2e395f9d0c884e1a13afbe026929d", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "70b272d0ee16e3c10a4f8cf0ef6152840828152e68f2f8e3046e89567f2b49ad"}, "spitfire": {:hex, :spitfire, "0.1.5", "10b041e781bff9544d2fdf00893e1a325758408c5366a9bfa4333072568659b1", [:mix], [], "hexpm", "866a55d21fe827934ff38200111335c9dd311df13cbf2580ed71d84b0a783150"}, - "splode": {:hex, :splode, "0.2.8", "289d4eec13e7a83061bc44827877eb4c575e1fdf198bd1a9c6449f9b64805059", [:mix], [], "hexpm", "dbe92fa526589416435e12203b56db1f74c834d207bc474016cedf930d987284"}, + "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, From 4bc657cf53e4e4021081adc3da951c6d1eb6b0a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Mar 2025 07:31:08 -0800 Subject: [PATCH 0933/1215] chore(deps-dev): bump git_ops in the dev-dependencies group (#500) Bumps the dev-dependencies group with 1 update: [git_ops](https://github.com/zachdaniel/git_ops). Updates `git_ops` from 2.7.1 to 2.7.2 - [Changelog](https://github.com/zachdaniel/git_ops/blob/master/CHANGELOG.md) - [Commits](https://github.com/zachdaniel/git_ops/compare/v2.7.1...v2.7.2) --- updated-dependencies: - dependency-name: git_ops dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index dd709088..be0b5734 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.7.1", "127a0a28925372472c37b5e012f872c3edb95ead2824682b5f6bd91180600772", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "006c6284a08129cc36ba5073dc1337917fa88e85ed98c409c8cc665db9c6a5d4"}, + "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "igniter": {:hex, :igniter, "0.5.31", "efee5efb94bcfce37d317ab54c54b979d8864bf9b76aafded82f7bb52a306076", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4f0c9456135444286ae89b22db10f8f1ad18932ccd4935bc14a5ad41704e5a1f"}, From 78b75a4295ae268b0b9b68ea8312cb8766662b51 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 08:21:03 -0800 Subject: [PATCH 0934/1215] fix: compose check constraints and base filters properly check constraints should ignore anything ignored by the base filter fixes #501 --- lib/migration_generator/operation.ex | 2 +- test/migration_generator_test.exs | 41 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 1dc72ad7..b3d4a28e 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1335,7 +1335,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do table: table }) do if base_filter do - "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"#{base_filter} AND #{check}\")", option(:prefix, schema)])}" + "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"(#{check}) OR NOT (#{base_filter})\")", option(:prefix, schema)])}" else "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"#{check}\")", option(:prefix, schema)])}" end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 88f76f67..706967fb 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1967,6 +1967,47 @@ defmodule AshPostgres.MigrationGeneratorTest do ~S[create constraint(:posts, :price_must_be_positive, check: "price > 0")] end + test "base filters are taken into account, negated" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:price, :integer, public?: true) + end + + resource do + base_filter(expr(price > 10)) + end + + postgres do + base_filter_sql "price > -10" + + check_constraints do + check_constraint(:price, "price_must_be_positive", check: "price > 0") + end + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert file = + "test_migration_path/**/*_migrate_resources*.exs" + |> Path.wildcard() + |> Enum.reject(&String.contains?(&1, "extensions")) + |> Enum.sort() + |> Enum.at(0) + |> File.read!() + + assert file =~ + ~S[create constraint(:posts, :price_must_be_positive, check: "(price > 0) OR NOT (price > -10)")] + end + test "when removed, the constraint is dropped before modification" do defposts do attributes do From 8be23b4a17f4b95b07d6f489ed808dc902ffca7f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 10:54:08 -0800 Subject: [PATCH 0935/1215] fix: handle CLI args better for ash_postgres.gen.resources --- lib/mix/tasks/ash_postgres.gen.resources.ex | 8 +++++--- lib/resource_generator/resource_generator.ex | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index bd6c4a36..03d87bfe 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -73,9 +73,11 @@ if Code.ensure_loaded?(Igniter) do options = options!(argv) repos = - options[:repo] || - Mix.Project.config()[:app] - |> Application.get_env(:ecto_repos, []) + case options[:repo] do + [] -> + Mix.Project.config()[:app] + |> Application.get_env(:ecto_repos, []) + end repos = repos diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index f67e79c3..ea03a95e 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -16,6 +16,24 @@ if Code.ensure_loaded?(Igniter) do opts = handle_csv_opts(opts, [:tables, :skip_tables, :extend]) + opts = + Keyword.update(opts, :tables, nil, fn tables -> + if tables == [] do + nil + else + tables + end + end) + + opts = + Keyword.update(opts, :skip_tables, nil, fn tables -> + if tables == [] do + nil + else + tables + end + end) + specs = repos |> Enum.flat_map(&Spec.tables(&1, skip_tables: opts[:skip_tables], tables: opts[:tables])) From d0cf6c2b8abc1271b961474f3bad3c0205e90897 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 10:54:55 -0800 Subject: [PATCH 0936/1215] chore: release version v2.5.8 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc401cfa..ea5995bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.8](https://github.com/ash-project/ash_postgres/compare/v2.5.7...v2.5.8) (2025-03-06) + + + + +### Bug Fixes: + +* handle CLI args better for ash_postgres.gen.resources + +* compose check constraints and base filters properly + ## [v2.5.7](https://github.com/ash-project/ash_postgres/compare/v2.5.6...v2.5.7) (2025-03-04) diff --git a/mix.exs b/mix.exs index 0cb046cd..f4214c34 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.7" + @version "2.5.8" def project do [ From 78d7fdda6ed01225b0ac115f6d8db7cf4d97de8b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 11:08:56 -0800 Subject: [PATCH 0937/1215] improvement: add `--public` option to `gen.resources`, default `true` this defaults attributes and relationships to public improvement: add `--default-actions` option to `gen.resources`, default `true` this adds default CRUD actions to the generated tables --- lib/mix/tasks/ash_postgres.gen.resources.ex | 8 +++ lib/resource_generator/resource_generator.ex | 59 ++++++++++++++++---- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 03d87bfe..f6044062 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -26,6 +26,8 @@ if Code.ensure_loaded?(Igniter) do - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. - `extend`, `e` - Extension or extensions to apply to the generated resources. See `mix ash.patch.extend` for more. - `yes`, `y` - Answer yes (or skip) to all questions. + - `default-actions` - Add default actions for each resource. Defaults to `true`. + - `public` - Mark all attributes and relationships as `public? true`. Defaults to `true`. ## Tables @@ -47,6 +49,8 @@ if Code.ensure_loaded?(Igniter) do yes: :boolean, tables: :keep, skip_tables: :keep, + default_actions: :boolean, + public: :boolean, extend: :keep, snapshots_only: :boolean, domain: :keep @@ -58,6 +62,10 @@ if Code.ensure_loaded?(Igniter) do e: :extend, d: :domain, s: :skip_tables + ], + defaults: [ + default_actions: true, + public: true ] } end diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index ea03a95e..f7845211 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -103,6 +103,8 @@ if Code.ensure_loaded?(Igniter) do domain: #{inspect(domain)}, data_layer: AshPostgres.DataLayer + #{default_actions(opts)} + postgres do table #{inspect(table_spec.table_name)} repo #{inspect(table_spec.repo)} @@ -115,11 +117,11 @@ if Code.ensure_loaded?(Igniter) do end attributes do - #{attributes(table_spec)} + #{attributes(table_spec, opts)} end """ |> add_identities(table_spec) - |> add_relationships(table_spec) + |> add_relationships(table_spec, opts) igniter |> Ash.Domain.Igniter.add_resource_reference(domain, table_spec.resource) @@ -135,6 +137,27 @@ if Code.ensure_loaded?(Igniter) do end) end + defp default_actions(opts) do + cond do + opts[:default_actions] && opts[:public] -> + """ + actions do + defaults [:read, :destroy, create: :*, update: :*] + end + """ + + opts[:default_actions] -> + """ + actions do + defaults [:read, :destroy, create: :*, update: :*] + end + """ + + true -> + "" + end + end + defp check_constraints(%{check_constraints: _check_constraints}, true) do "" end @@ -257,14 +280,14 @@ if Code.ensure_loaded?(Igniter) do defp add_nils_distinct?(str, _), do: str - defp add_relationships(str, %{relationships: []}) do + defp add_relationships(str, %{relationships: []}, _opts) do str end - defp add_relationships(str, %{relationships: relationships} = spec) do + defp add_relationships(str, %{relationships: relationships} = spec, opts) do relationships |> Enum.map_join("\n", fn relationship -> - case relationship_options(spec, relationship) do + case relationship_options(spec, relationship, opts) do "" -> "#{relationship.type} :#{relationship.name}, #{inspect(relationship.destination)}" @@ -287,7 +310,7 @@ if Code.ensure_loaded?(Igniter) do end) end - defp relationship_options(spec, %{type: :belongs_to} = rel) do + defp relationship_options(spec, %{type: :belongs_to} = rel, opts) do case Enum.find(spec.attributes, fn attribute -> attribute.name == rel.source_attribute end) do @@ -303,6 +326,7 @@ if Code.ensure_loaded?(Igniter) do |> add_source_attribute(rel, "#{rel.name}_id") |> add_allow_nil(rel) |> add_filter(rel) + |> add_public(opts) attribute -> "" @@ -312,10 +336,11 @@ if Code.ensure_loaded?(Igniter) do |> add_primary_key(attribute.primary_key?) |> add_attribute_type(attribute) |> add_filter(rel) + |> add_public(opts) end end - defp relationship_options(_spec, rel) do + defp relationship_options(_spec, rel, opts) do default_destination_attribute = rel.source |> Module.split() @@ -327,6 +352,7 @@ if Code.ensure_loaded?(Igniter) do |> add_destination_attribute(rel, default_destination_attribute) |> add_source_attribute(rel, "id") |> add_filter(rel) + |> add_public(opts) end defp add_filter(str, %{match_with: []}), do: str @@ -544,7 +570,7 @@ if Code.ensure_loaded?(Igniter) do defp add_include(str, include), do: str <> "\ninclude [#{Enum.map_join(include, ", ", &inspect/1)}]" - defp attributes(table_spec) do + defp attributes(table_spec, opts) do table_spec.attributes |> Enum.split_with(& &1.default) |> then(fn {l, r} -> r ++ l end) @@ -560,10 +586,10 @@ if Code.ensure_loaded?(Igniter) do end) end end) - |> Enum.map_join("\n", &attribute(&1)) + |> Enum.map_join("\n", &attribute(&1, opts)) end - defp attribute(attribute) do + defp attribute(attribute, opts) do now_default = &DateTime.utc_now/0 uuid_default = &Ash.UUID.generate/0 @@ -593,7 +619,7 @@ if Code.ensure_loaded?(Igniter) do {"attribute", attribute, true, false} end - case String.trim(options(attribute, type_option?)) do + case String.trim(options(attribute, type_option?, opts)) do "" -> if type? do "#{constructor} :#{attribute.name}, #{inspect(attribute.attr_type)}" @@ -618,7 +644,7 @@ if Code.ensure_loaded?(Igniter) do end end - defp options(attribute, type_option?) do + defp options(attribute, type_option?, opts) do "" |> add_primary_key(attribute) |> add_allow_nil(attribute) @@ -626,9 +652,18 @@ if Code.ensure_loaded?(Igniter) do |> add_default(attribute) |> add_type(attribute, type_option?) |> add_generated(attribute) + |> add_public(opts) |> add_source(attribute) end + defp add_public(str, options) do + if options[:public] do + str <> "\n public? true" + else + str + end + end + defp add_type(str, %{attr_type: attr_type}, true) do str <> "\n type #{inspect(attr_type)}" end From a7fbfe38a843e1618be8ec9846a4e8675d3e02ea Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 11:17:01 -0800 Subject: [PATCH 0938/1215] fix: match on non-empty repo options --- lib/mix/tasks/ash_postgres.gen.resources.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index f6044062..dbe31377 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -85,6 +85,9 @@ if Code.ensure_loaded?(Igniter) do [] -> Mix.Project.config()[:app] |> Application.get_env(:ecto_repos, []) + + repos -> + repos end repos = From 0f9a0130608b45bd5828c387027cf8feed73775d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 11:19:36 -0800 Subject: [PATCH 0939/1215] chore: fix tests --- test/resource_generator_test.exs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index 30a14a1a..c50292aa 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -32,18 +32,31 @@ defmodule AshPostgres.ResourceGeenratorTests do domain: MyApp.Accounts, data_layer: AshPostgres.DataLayer + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + postgres do table("example_table") repo(AshPostgres.TestRepo) end attributes do - uuid_primary_key(:id) - attribute(:name, :string) - attribute(:age, :integer) + uuid_primary_key :id do + public?(true) + end + + attribute :name, :string do + public?(true) + end + + attribute :age, :integer do + public?(true) + end attribute :email, :string do sensitive?(true) + public?(true) end end end From f61fa269246bbcc0d0f1d5db06ebb47a4ebf4167 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 11:19:45 -0800 Subject: [PATCH 0940/1215] chore: release version v2.5.9 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5995bf..47c09f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.9](https://github.com/ash-project/ash_postgres/compare/v2.5.8...v2.5.9) (2025-03-06) + + + + +### Bug Fixes: + +* match on non-empty repo options + +### Improvements: + +* add `--public` option to `gen.resources`, default `true` + +* add `--default-actions` option to `gen.resources`, default `true` + ## [v2.5.8](https://github.com/ash-project/ash_postgres/compare/v2.5.7...v2.5.8) (2025-03-06) diff --git a/mix.exs b/mix.exs index f4214c34..0a9982c8 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.8" + @version "2.5.9" def project do [ From c3d7adbe59efe6a053cf497a238b97bc4448cd73 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 14:26:27 -0800 Subject: [PATCH 0941/1215] improvement: never import `schema_migrations` table --- lib/mix/tasks/ash_postgres.gen.resources.ex | 2 +- lib/resource_generator/resource_generator.ex | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index dbe31377..44af31cf 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -22,7 +22,7 @@ if Code.ensure_loaded?(Igniter) do - `repo`, `r` - The repo or repos to generate resources for, comma separated. Can be specified multiple times. Defaults to all repos. - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. - - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. + - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. `schema_migrations` is always skipped. - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. - `extend`, `e` - Extension or extensions to apply to the generated resources. See `mix ash.patch.extend` for more. - `yes`, `y` - Answer yes (or skip) to all questions. diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index f7845211..d44cc296 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -28,10 +28,11 @@ if Code.ensure_loaded?(Igniter) do opts = Keyword.update(opts, :skip_tables, nil, fn tables -> if tables == [] do - nil + [] else tables end + |> Enum.concat("schema_migrations") end) specs = From f1d2815ca39ea5c5be8a427b7a2451e5b5db0d0d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 14:32:24 -0800 Subject: [PATCH 0942/1215] fix: honor skip_tables --- lib/resource_generator/resource_generator.ex | 3 ++- lib/resource_generator/spec.ex | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index d44cc296..c6a7c5e3 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -32,8 +32,9 @@ if Code.ensure_loaded?(Igniter) do else tables end - |> Enum.concat("schema_migrations") + |> Enum.concat(["schema_migrations"]) end) + |> IO.inspect() specs = repos diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index a813e600..5df21716 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -114,6 +114,9 @@ defmodule AshPostgres.ResourceGenerator.Spec do |> add_indexes() |> add_check_constraints() end) + |> Enum.reject(fn spec -> + spec.table_name in List.wrap(opts[:skip_tables]) + end) end) result From e9b544534b4b47d30881feddfab42d62d34c65fb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 14:32:55 -0800 Subject: [PATCH 0943/1215] chore: remove IO.inspect --- lib/resource_generator/resource_generator.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index c6a7c5e3..3b917d4c 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -34,7 +34,6 @@ if Code.ensure_loaded?(Igniter) do end |> Enum.concat(["schema_migrations"]) end) - |> IO.inspect() specs = repos From 17406f34e48db57b4cb232aac2a886f7457d85f9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 14:34:14 -0800 Subject: [PATCH 0944/1215] chore: release version v2.5.10 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c09f98..b80e9117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.10](https://github.com/ash-project/ash_postgres/compare/v2.5.9...v2.5.10) (2025-03-06) + + + + +### Bug Fixes: + +* honor skip_tables + +### Improvements: + +* never import `schema_migrations` table + ## [v2.5.9](https://github.com/ash-project/ash_postgres/compare/v2.5.8...v2.5.9) (2025-03-06) diff --git a/mix.exs b/mix.exs index 0a9982c8..34035006 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.9" + @version "2.5.10" def project do [ From ccbb2a80380b181110880f85a81d0a1148ffd2b1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 17:36:34 -0800 Subject: [PATCH 0945/1215] fix: only configure repo in installer if not already configured fix: install ash if not installed already --- lib/mix/tasks/ash_postgres.install.ex | 330 +++++++++++++++----------- 1 file changed, 193 insertions(+), 137 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 636fe999..0a14c370 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -35,6 +35,26 @@ if Code.ensure_loaded?(Igniter) do otp_app = Igniter.Project.Application.app_name(igniter) + igniter = + if Igniter.Project.Deps.has_dep?(igniter, :ash) do + igniter + |> Igniter.Project.Deps.add_dep({:ash, "~> 3.0"}, yes: opts[:yes]) + |> then(fn + %{assigns: %{test_mode?: true}} = igniter -> + igniter + + igniter -> + Igniter.apply_and_fetch_dependencies(igniter, + error_on_abort?: true, + yes: opts[:yes], + yes_to_deps: true + ) + end) + |> Igniter.compose_task("ash.install", argv) + else + igniter + end + igniter |> Igniter.Project.Formatter.import_dep(:ash_postgres) |> setup_aliases() @@ -117,75 +137,97 @@ if Code.ensure_loaded?(Igniter) do end defp configure_runtime(igniter, otp_app, repo) do - default_runtime = """ - import Config - - if config_env() == :prod do - database_url = - System.get_env("DATABASE_URL") || - raise \"\"\" - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - \"\"\" - - config #{inspect(otp_app)}, #{inspect(repo)}, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") - end - """ - - igniter - |> Igniter.create_or_update_elixir_file("config/runtime.exs", default_runtime, fn zipper -> - if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo, :url]) do - zipper - else - patterns = [ - """ - if config_env() == :prod do - __cursor__() - end - """, - """ - if :prod == config_env() do - __cursor__() - end - """ - ] + if Igniter.Project.Config.configures_key?(igniter, "runtime.exs", otp_app, [repo]) do + igniter + else + default_runtime = """ + import Config + + if config_env() == :prod do + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, #{inspect(repo)}, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + end + """ - zipper - |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) - |> case do - {:ok, zipper} -> - case Igniter.Code.Function.move_to_function_call_in_current_scope( - zipper, - :=, - 2, - fn call -> - Igniter.Code.Function.argument_matches_pattern?( - call, - 0, - {:database_url, _, ctx} when is_atom(ctx) - ) - end - ) do - {:ok, _zipper} -> - zipper - |> Igniter.Project.Config.modify_configuration_code( - [repo, :url], - otp_app, - {:database_url, [], nil} - ) - |> Igniter.Project.Config.modify_configuration_code( - [repo, :pool_size], - otp_app, - Sourceror.parse_string!(""" - String.to_integer(System.get_env("POOL_SIZE") || "10") - """) - ) - |> then(&{:ok, &1}) - - _ -> + igniter + |> Igniter.create_or_update_elixir_file( + "config/runtime.exs", + default_runtime, + fn zipper -> + if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo, :url]) do + zipper + else + patterns = [ + """ + if config_env() == :prod do + __cursor__() + end + """, + """ + if :prod == config_env() do + __cursor__() + end + """ + ] + + zipper + |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) + |> case do + {:ok, zipper} -> + case Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :=, + 2, + fn call -> + Igniter.Code.Function.argument_matches_pattern?( + call, + 0, + {:database_url, _, ctx} when is_atom(ctx) + ) + end + ) do + {:ok, _zipper} -> + zipper + |> Igniter.Project.Config.modify_configuration_code( + [repo, :url], + otp_app, + {:database_url, [], nil} + ) + |> Igniter.Project.Config.modify_configuration_code( + [repo, :pool_size], + otp_app, + Sourceror.parse_string!(""" + String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + ) + |> then(&{:ok, &1}) + + _ -> + Igniter.Code.Common.add_code(zipper, """ + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, Helpdesk.Repo, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + end + + :error -> Igniter.Code.Common.add_code(zipper, """ + if config_env() == :prod do database_url = System.get_env("DATABASE_URL") || raise \"\"\" @@ -196,86 +238,100 @@ if Code.ensure_loaded?(Igniter) do config #{inspect(otp_app)}, Helpdesk.Repo, url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") + end """) end - - :error -> - Igniter.Code.Common.add_code(zipper, """ - if config_env() == :prod do - database_url = - System.get_env("DATABASE_URL") || - raise \"\"\" - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - \"\"\" - - config #{inspect(otp_app)}, Helpdesk.Repo, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") - end - """) + end end - end - end) + ) + end end defp configure_dev(igniter, otp_app, repo) do - igniter - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :username], "postgres") - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :password], "postgres") - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :hostname], "localhost") - |> Igniter.Project.Config.configure_new( - "dev.exs", - otp_app, - [repo, :database], - "#{otp_app}_dev" - ) - |> Igniter.Project.Config.configure_new( - "dev.exs", - otp_app, - [repo, :show_sensitive_data_on_connection_error], - true - ) - |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :pool_size], 10) + if Igniter.Project.Config.configures_key?(igniter, "dev.exs", otp_app, [repo]) do + igniter + else + igniter + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :username], "postgres") + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :password], "postgres") + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :hostname], + "localhost" + ) + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :database], + "#{otp_app}_dev" + ) + |> Igniter.Project.Config.configure_new( + "dev.exs", + otp_app, + [repo, :show_sensitive_data_on_connection_error], + true + ) + |> Igniter.Project.Config.configure_new("dev.exs", otp_app, [repo, :pool_size], 10) + end end defp configure_test(igniter, otp_app, repo) do - database = - {:<<>>, [], - [ - "#{otp_app}_test", - {:"::", [], - [ - {{:., [], [Kernel, :to_string]}, [from_interpolation: true], - [ - {{:., [], [{:__aliases__, [alias: false], [:System]}, :get_env]}, [], - ["MIX_TEST_PARTITION"]} - ]}, - {:binary, [], Elixir} - ]} - ]} - |> Sourceror.to_string() - |> Sourceror.parse_string!() + if Igniter.Project.Config.configures_key?(igniter, "test.exs", otp_app, [repo]) do + igniter + else + database = + {:<<>>, [], + [ + "#{otp_app}_test", + {:"::", [], + [ + {{:., [], [Kernel, :to_string]}, [from_interpolation: true], + [ + {{:., [], [{:__aliases__, [alias: false], [:System]}, :get_env]}, [], + ["MIX_TEST_PARTITION"]} + ]}, + {:binary, [], Elixir} + ]} + ]} + |> Sourceror.to_string() + |> Sourceror.parse_string!() - igniter - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :username], "postgres") - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :password], "postgres") - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :hostname], "localhost") - |> Igniter.Project.Config.configure_new( - "test.exs", - otp_app, - [repo, :database], - {:code, database} - ) - |> Igniter.Project.Config.configure_new( - "test.exs", - otp_app, - [repo, :pool], - Ecto.Adapters.SQL.Sandbox - ) - |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) - |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) - |> Igniter.Project.Config.configure_new("test.exs", :logger, [:level], :warning) + igniter + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :username], + "postgres" + ) + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :password], + "postgres" + ) + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :hostname], + "localhost" + ) + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :database], + {:code, database} + ) + |> Igniter.Project.Config.configure_new( + "test.exs", + otp_app, + [repo, :pool], + Ecto.Adapters.SQL.Sandbox + ) + |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) + |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) + |> Igniter.Project.Config.configure_new("test.exs", :logger, [:level], :warning) + end end defp setup_data_case(igniter) do From fd1aaea4e4d48e35ecae828d43520162d2184ef5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 17:45:14 -0800 Subject: [PATCH 0946/1215] =?UTF-8?q?fix:=20don't=20modify=20repo=20in=20r?= =?UTF-8?q?untime.exs=20fix:=20remove=20Helpdesk.Repo=20from=20installer?= =?UTF-8?q?=20=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/mix/tasks/ash_postgres.install.ex | 90 ++++++++++++++------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 0a14c370..25edef3a 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -162,8 +162,8 @@ if Code.ensure_loaded?(Igniter) do "config/runtime.exs", default_runtime, fn zipper -> - if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo, :url]) do - zipper + if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo]) do + {:ok, zipper} else patterns = [ """ @@ -182,47 +182,51 @@ if Code.ensure_loaded?(Igniter) do |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> - case Igniter.Code.Function.move_to_function_call_in_current_scope( - zipper, - :=, - 2, - fn call -> - Igniter.Code.Function.argument_matches_pattern?( - call, - 0, - {:database_url, _, ctx} when is_atom(ctx) - ) - end - ) do - {:ok, _zipper} -> - zipper - |> Igniter.Project.Config.modify_configuration_code( - [repo, :url], - otp_app, - {:database_url, [], nil} - ) - |> Igniter.Project.Config.modify_configuration_code( - [repo, :pool_size], - otp_app, - Sourceror.parse_string!(""" - String.to_integer(System.get_env("POOL_SIZE") || "10") + if Igniter.Project.Config.configures_key?(zipper, "runtime.exs", otp_app, [repo]) do + {:ok, zipper} + else + case Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :=, + 2, + fn call -> + Igniter.Code.Function.argument_matches_pattern?( + call, + 0, + {:database_url, _, ctx} when is_atom(ctx) + ) + end + ) do + {:ok, _zipper} -> + zipper + |> Igniter.Project.Config.modify_configuration_code( + [repo, :url], + otp_app, + {:database_url, [], nil} + ) + |> Igniter.Project.Config.modify_configuration_code( + [repo, :pool_size], + otp_app, + Sourceror.parse_string!(""" + String.to_integer(System.get_env("POOL_SIZE") || "10") + """) + ) + |> then(&{:ok, &1}) + + _ -> + Igniter.Code.Common.add_code(zipper, """ + database_url = + System.get_env("DATABASE_URL") || + raise \"\"\" + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + \"\"\" + + config #{inspect(otp_app)}, #{inspect(repo)}, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") """) - ) - |> then(&{:ok, &1}) - - _ -> - Igniter.Code.Common.add_code(zipper, """ - database_url = - System.get_env("DATABASE_URL") || - raise \"\"\" - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - \"\"\" - - config #{inspect(otp_app)}, Helpdesk.Repo, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") - """) + end end :error -> @@ -235,7 +239,7 @@ if Code.ensure_loaded?(Igniter) do For example: ecto://USER:PASS@HOST/DATABASE \"\"\" - config #{inspect(otp_app)}, Helpdesk.Repo, + config #{inspect(otp_app)}, #{inspect(repo)}, url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") end From c9dba9f011d351093ac4460d92a2934210889f8d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 17:49:39 -0800 Subject: [PATCH 0947/1215] fix: use `configures_key?/3` --- lib/mix/tasks/ash_postgres.install.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 25edef3a..d2587f7d 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -169,6 +169,7 @@ if Code.ensure_loaded?(Igniter) do """ if config_env() == :prod do __cursor__() + end """, """ @@ -182,7 +183,7 @@ if Code.ensure_loaded?(Igniter) do |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> - if Igniter.Project.Config.configures_key?(zipper, "runtime.exs", otp_app, [repo]) do + if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo]) do {:ok, zipper} else case Igniter.Code.Function.move_to_function_call_in_current_scope( From bfd0100b1d6fc68077d2df124bdc025de0ac2003 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 17:54:31 -0800 Subject: [PATCH 0948/1215] fix: go to top of if block --- lib/mix/tasks/ash_postgres.install.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index d2587f7d..dabc3ac0 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -169,7 +169,6 @@ if Code.ensure_loaded?(Igniter) do """ if config_env() == :prod do __cursor__() - end """, """ @@ -179,6 +178,8 @@ if Code.ensure_loaded?(Igniter) do """ ] + zipper = zipper |> Sourceror.Zipper.up() |> Sourceror.Zipper.down() + zipper |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do From 3264f3b19c6962083be224b19ec67a7eef9211ce Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 17:56:19 -0800 Subject: [PATCH 0949/1215] fix: put move up/down in the right place --- lib/mix/tasks/ash_postgres.install.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index dabc3ac0..ddec5373 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -178,12 +178,12 @@ if Code.ensure_loaded?(Igniter) do """ ] - zipper = zipper |> Sourceror.Zipper.up() |> Sourceror.Zipper.down() - zipper |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> + zipper = zipper |> Sourceror.Zipper.up() |> Sourceror.Zipper.down() + if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo]) do {:ok, zipper} else From a2f53b43c50fa171884389be4dc77a2bd8fb7552 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 6 Mar 2025 17:58:17 -0800 Subject: [PATCH 0950/1215] chore: undo bad fix --- lib/mix/tasks/ash_postgres.install.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index ddec5373..555d89e7 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -182,8 +182,6 @@ if Code.ensure_loaded?(Igniter) do |> Igniter.Code.Common.move_to_cursor_match_in_scope(patterns) |> case do {:ok, zipper} -> - zipper = zipper |> Sourceror.Zipper.up() |> Sourceror.Zipper.down() - if Igniter.Project.Config.configures_key?(zipper, otp_app, [repo]) do {:ok, zipper} else From a0ca24250407a3d38333521b7333dcff8df493c8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 09:41:07 -0800 Subject: [PATCH 0951/1215] fix: allow optional input for relationship name guesser fixes #503 --- lib/resource_generator/spec.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 5df21716..6f90b53f 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -777,7 +777,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do |> String.trim_trailing() end - Owl.IO.input(label: label) + Owl.IO.input(label: label, optional: true) |> String.trim() # common typo |> String.trim_leading(":") From c0b49720fecb4ac6f784aeebe1f31b1d22eb1d9e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 09:48:52 -0800 Subject: [PATCH 0952/1215] improvement: add `skip_unknown` option to `ash_postgres.gen.resources` --- lib/mix/tasks/ash_postgres.gen.resources.ex | 1 + lib/resource_generator/spec.ex | 88 +++++++++++---------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 44af31cf..a47a0048 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -52,6 +52,7 @@ if Code.ensure_loaded?(Igniter) do default_actions: :boolean, public: :boolean, extend: :keep, + skip_unknown: :boolean, snapshots_only: :boolean, domain: :keep ], diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 6f90b53f..95aab278 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -738,57 +738,61 @@ defmodule AshPostgres.ResourceGenerator.Spec do defp name_all_relationships(_type, _opts, _spec, _name, [], acc), do: acc defp name_all_relationships(type, opts, spec, name, [relationship | rest], acc) do - label = - case type do - :belongs_to -> - """ - Multiple foreign keys found from `#{inspect(spec.resource)}` to `#{inspect(relationship.destination)}` with the guessed name `#{name}`. + if opts[:yes?] || opts[:skip_unknown] do + name_all_relationships(type, opts, spec, name, rest, acc) + else + label = + case type do + :belongs_to -> + """ + Multiple foreign keys found from `#{inspect(spec.resource)}` to `#{inspect(relationship.destination)}` with the guessed name `#{name}`. - Provide a relationship name for the one with the following info: + Provide a relationship name for the one with the following info: - Resource: `#{inspect(spec.resource)}` - Relationship Type: :belongs_to - Guessed Name: `:#{name}` - Relationship Destination: `#{inspect(relationship.destination)}` - Constraint Name: `#{inspect(relationship.constraint_name)}`. + Resource: `#{inspect(spec.resource)}` + Relationship Type: :belongs_to + Guessed Name: `:#{name}` + Relationship Destination: `#{inspect(relationship.destination)}` + Constraint Name: `#{inspect(relationship.constraint_name)}`. - Leave empty to skip adding this relationship. + Leave empty to skip adding this relationship. - Name: - """ - |> String.trim_trailing() + Name: + """ + |> String.trim_trailing() - _ -> - """ - Multiple foreign keys found from `#{inspect(relationship.source)}` to `#{inspect(spec.resource)}` with the guessed name `#{name}`. + _ -> + """ + Multiple foreign keys found from `#{inspect(relationship.source)}` to `#{inspect(spec.resource)}` with the guessed name `#{name}`. - Provide a relationship name for the one with the following info: + Provide a relationship name for the one with the following info: - Resource: `#{inspect(relationship.source)}` - Relationship Type: :#{relationship.type} - Guessed Name: `:#{name}` - Relationship Destination: `#{inspect(spec.resource)}` - Constraint Name: `#{inspect(relationship.constraint_name)}`. + Resource: `#{inspect(relationship.source)}` + Relationship Type: :#{relationship.type} + Guessed Name: `:#{name}` + Relationship Destination: `#{inspect(spec.resource)}` + Constraint Name: `#{inspect(relationship.constraint_name)}`. - Leave empty to skip adding this relationship. + Leave empty to skip adding this relationship. - Name: - """ - |> String.trim_trailing() - end + Name: + """ + |> String.trim_trailing() + end - Owl.IO.input(label: label, optional: true) - |> String.trim() - # common typo - |> String.trim_leading(":") - |> case do - "" -> - name_all_relationships(type, opts, spec, name, rest, acc) - - new_name -> - name_all_relationships(type, opts, spec, name, rest, [ - %{relationship | name: new_name} | acc - ]) + Owl.IO.input(label: label, optional: true) + |> String.trim() + # common typo + |> String.trim_leading(":") + |> case do + "" -> + name_all_relationships(type, opts, spec, name, rest, acc) + + new_name -> + name_all_relationships(type, opts, spec, name, rest, [ + %{relationship | name: new_name} | acc + ]) + end end end @@ -888,7 +892,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do # sobelow_skip ["RCE.CodeModule", "DOS.StringToAtom"] defp get_type(attribute, opts) do result = - if opts[:yes?] do + if opts[:yes?] || opts[:skip_unknown] do "skip" else Mix.shell().prompt(""" From 4dc86d6d38b0fd61706ee4ac8514954c48cec5d3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 09:57:26 -0800 Subject: [PATCH 0953/1215] improvement: document options, add `--no-migrations` --- lib/mix/tasks/ash_postgres.gen.resources.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index a47a0048..17960544 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -28,6 +28,9 @@ if Code.ensure_loaded?(Igniter) do - `yes`, `y` - Answer yes (or skip) to all questions. - `default-actions` - Add default actions for each resource. Defaults to `true`. - `public` - Mark all attributes and relationships as `public? true`. Defaults to `true`. + - `no-migrations` - Do not generate snapshots & migrations for the resources. Defaults to `false`. + - `skip-unknown` - Skip any attributes with types that we don't have a corresponding Elixir type for, and relationships that we can't assume the name of. + - `public` - Mark all attributes and relationships as `public? true`. Defaults to `true`. ## Tables @@ -53,6 +56,7 @@ if Code.ensure_loaded?(Igniter) do public: :boolean, extend: :keep, skip_unknown: :boolean, + migrations: :boolean, snapshots_only: :boolean, domain: :keep ], @@ -66,6 +70,7 @@ if Code.ensure_loaded?(Igniter) do ], defaults: [ default_actions: true, + migrations: true, public: true ] } @@ -141,7 +146,7 @@ if Code.ensure_loaded?(Igniter) do """ options = - if options[:yes] || Mix.shell().yes?(prompt) do + if options[:migrations] || options[:yes] || Mix.shell().yes?(prompt) do Keyword.put(options, :no_migrations, false) else Keyword.put(options, :no_migrations, true) From 36a11efbcb3d182ae35fc8060e01da4d563dc67b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 10:17:55 -0800 Subject: [PATCH 0954/1215] fix: honor --no-migrations flag --- lib/mix/tasks/ash_postgres.gen.resources.ex | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 17960544..99c6a158 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -146,10 +146,15 @@ if Code.ensure_loaded?(Igniter) do """ options = - if options[:migrations] || options[:yes] || Mix.shell().yes?(prompt) do - Keyword.put(options, :no_migrations, false) - else - Keyword.put(options, :no_migrations, true) + cond do + options[:migrations] == false -> + Keyword.put(options, :no_migrations, false) + + options[:migrations] || options[:yes] || Mix.shell().yes?(prompt) -> + Keyword.put(options, :no_migrations, false) + + true -> + Keyword.put(options, :no_migrations, true) end migration_opts = From b7580fe07e4bb915adcdbc1c894780b6588f816c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 10:24:20 -0800 Subject: [PATCH 0955/1215] fix: honor skip_unknown option in spec table generator --- lib/resource_generator/resource_generator.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 3b917d4c..dbc8df14 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -37,7 +37,13 @@ if Code.ensure_loaded?(Igniter) do specs = repos - |> Enum.flat_map(&Spec.tables(&1, skip_tables: opts[:skip_tables], tables: opts[:tables])) + |> Enum.flat_map( + &Spec.tables(&1, + skip_tables: opts[:skip_tables], + tables: opts[:tables], + skip_unknown: opts[:skip_unknown] + ) + ) |> Enum.map(fn %{table_name: table} = spec -> resource = table From f9120f6b4f212be4ff661fd18eb5191a9e5e1ca4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 10:40:12 -0800 Subject: [PATCH 0956/1215] fix: ignore attributes with no known type --- lib/resource_generator/spec.ex | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 95aab278..6e3d17dc 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -872,19 +872,23 @@ defmodule AshPostgres.ResourceGenerator.Spec do def set_types(attributes, opts) do attributes - |> Enum.map(fn attribute -> + |> Enum.flat_map(fn attribute -> case Process.get({:type_cache, attribute.type}) do nil -> case type(attribute.type) do {:ok, type} -> - %{attribute | attr_type: type} + [%{attribute | attr_type: type}] :error -> - get_type(attribute, opts) + case get_type(attribute, opts) do + :skip -> [] + {:ok, type} -> [%{attribute | attr_type: type}] + :error -> [] + end end type -> - %{attribute | attr_type: type} + [%{attribute | attr_type: type}] end end) end @@ -895,6 +899,8 @@ defmodule AshPostgres.ResourceGenerator.Spec do if opts[:yes?] || opts[:skip_unknown] do "skip" else + raise "what" + Mix.shell().prompt(""" Unknown type: #{attribute.type}. What should we use as the type? @@ -911,20 +917,20 @@ defmodule AshPostgres.ResourceGenerator.Spec do case result do skip when skip in ["skip", "skip\n"] -> - attribute + :skip new_type -> case String.trim(new_type) do ":" <> type -> new_type = String.to_atom(type) Process.put({:type_cache, attribute.type}, new_type) - %{attribute | attr_type: new_type} + {:ok, %{attribute | attr_type: new_type}} type -> try do Code.eval_string(type) Process.put({:type_cache, attribute.type}, new_type) - %{attribute | attr_type: new_type} + {:ok, %{attribute | attr_type: new_type}} rescue _e -> get_type(attribute, opts) From 20cbf92160bf374566836157af12c694c8b88fa1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 7 Mar 2025 10:46:36 -0800 Subject: [PATCH 0957/1215] chore: fix typo --- lib/mix/tasks/ash_postgres.gen.resources.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 99c6a158..9a36d7a9 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -148,7 +148,7 @@ if Code.ensure_loaded?(Igniter) do options = cond do options[:migrations] == false -> - Keyword.put(options, :no_migrations, false) + Keyword.put(options, :no_migrations, true) options[:migrations] || options[:yes] || Mix.shell().yes?(prompt) -> Keyword.put(options, :no_migrations, false) From fe13e0823626c163a8d79f318f3d847edd9d79f5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 11 Mar 2025 13:28:20 -0400 Subject: [PATCH 0958/1215] chore: release version v2.5.11 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b80e9117..0b241858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,41 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.11](https://github.com/ash-project/ash_postgres/compare/v2.5.10...v2.5.11) (2025-03-11) + + + + +### Bug Fixes: + +* ignore attributes with no known type + +* honor skip_unknown option in spec table generator + +* honor --no-migrations flag + +* allow optional input for relationship name guesser + +* put move up/down in the right place + +* go to top of if block + +* use `configures_key?/3` + +* don't modify repo in runtime.exs + +* remove Helpdesk.Repo from installer 🤦 + +* only configure repo in installer if not already configured + +* install ash if not installed already + +### Improvements: + +* document options, add `--no-migrations` + +* add `skip_unknown` option to `ash_postgres.gen.resources` + ## [v2.5.10](https://github.com/ash-project/ash_postgres/compare/v2.5.9...v2.5.10) (2025-03-06) diff --git a/mix.exs b/mix.exs index 34035006..0eb1a171 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.10" + @version "2.5.11" def project do [ From 2b767bee8ce32db18c7c9b76d8dd9cd0a62d81b1 Mon Sep 17 00:00:00 2001 From: Mike Wilson Date: Tue, 11 Mar 2025 10:35:42 -0700 Subject: [PATCH 0959/1215] Don't use multitenancy attribute in composite index key when setting up tenant reference (#507) --- lib/migration_generator/operation.ex | 20 ++++++---- test/migration_generator_test.exs | 58 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index b3d4a28e..87cea347 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1012,10 +1012,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do multitenancy: multitenancy }) do keys = - if multitenancy.strategy == :attribute do - [multitenancy.attribute, source] - else - [source] + case multitenancy do + %{strategy: :attribute, attribute: attribute} when attribute != source -> + [attribute, source] + + _ -> + [source] end opts = @@ -1032,10 +1034,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do def down(%{schema: schema, source: source, table: table, multitenancy: multitenancy}) do keys = - if multitenancy.strategy == :attribute do - [multitenancy.attribute, source] - else - [source] + case multitenancy do + %{strategy: :attribute, attribute: attribute} when attribute != source -> + [attribute, source] + + _ -> + [source] end opts = diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 706967fb..2e9bd9e9 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1500,6 +1500,64 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(file) =~ ~S{create index(:posts, [:org_id, :post_id])} end + test "index generated by index? true does not duplicate tenant column when using attribute multitenancy if reference is same as tenant column" do + defresource Org, "orgs" do + attributes do + uuid_primary_key(:id, writable?: true, public?: true) + attribute(:name, :string, public?: true) + end + + multitenancy do + strategy(:attribute) + attribute(:id) + end + end + + defposts do + attributes do + uuid_primary_key(:id) + attribute(:key_id, :uuid, allow_nil?: false, public?: true) + attribute(:foobar, :string, public?: true) + end + + multitenancy do + strategy(:attribute) + attribute(:org_id) + end + + relationships do + belongs_to(:org, Org) do + public?(true) + end + end + + postgres do + references do + reference(:org, index?: true) + end + end + end + + defdomain([Org, Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert file = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) + |> File.read!() + + assert [up_code, down_code] = String.split(file, "def down do") + + assert up_code =~ ~S{create index(:posts, [:org_id])} + assert down_code =~ ~S{drop_if_exists index(:posts, [:org_id])} + end + test "references merge :match_with and multitenancy attribute" do defresource Org, "orgs" do attributes do From c14c160b815ff8aec5ce78dbbc319c84e88fe9e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 06:26:35 -0400 Subject: [PATCH 0960/1215] chore(deps): bump the production-dependencies group with 3 updates (#508) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.4.67 to 3.4.68 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.67...v3.4.68) Updates `ash_sql` from 0.2.60 to 0.2.61 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.60...v0.2.61) Updates `igniter` from 0.5.31 to 0.5.35 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.31...v0.5.35) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mix.lock b/mix.lock index be0b5734..6195ddff 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.67", "b2e3e65ddff550998a5c8dd1fc7f8ef4d7ebfe75a474c1e620e9b51b3ff5f70d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "770a637208ef0be5b9778687efb55be7b4f75d0202f80ddc4b1b918a8282ba28"}, - "ash_sql": {:hex, :ash_sql, "0.2.60", "3920e05ba34df913f4e32c7395480687a0372db7c2725b7c7c390b432e28be08", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "dcc3eb8277ccb456cd9ff1e4d9b06699ce9f0502c1dfefee388eb1850817e77e"}, + "ash": {:hex, :ash, "3.4.68", "d6478880cf11500d4cc15a23c73ef78dc0471a4c59891ec925124a0922df2746", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bb8249b45a48de04664fa73ab2cbbc3ba56b792ced4997bdc6862c39f3fe47b4"}, + "ash_sql": {:hex, :ash_sql, "0.2.61", "ef6de767e67613b6b1cf39da525cf72c3581f367506c38624e6a8f4506192d3c", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8555d1e68c3fe7408cd3a5383e5a881b55204ce0c95cddf18dddf8c0fe7e9ca2"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.31", "efee5efb94bcfce37d317ab54c54b979d8864bf9b76aafded82f7bb52a306076", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4f0c9456135444286ae89b22db10f8f1ad18932ccd4935bc14a5ad41704e5a1f"}, + "igniter": {:hex, :igniter, "0.5.35", "ebc02429d7ea80b44c861018051f2cd6ca5e45297ed1e5041ee3ed82f39ffd92", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "0d0dc13cc22636649d9e5dfd7ac13207f179523673fc0d097aa9ea98e29189c2"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,14 +39,14 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.14.0", "8dc5d4946391010bf9fa7b58dd1e75d3c1cf97315e5489b7797cf64b82ae27a4", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9cf5068e4042791c150f0dfbc00f4f435433eb948036b44b95b940e457b35a6a"}, + "reactor": {:hex, :reactor, "0.15.0", "556937d9310e1a6dd06083592b9eb9e0d212540b6d82faecba70823ee7a0747d", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f634383a7760ba3106d31a3185f2e2c39e1485d899d884d94c22c62c9b5e7a4a"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.45", "19e3a879e80d02853ded85ed7b4c0a84a5d2e395f9d0c884e1a13afbe026929d", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "70b272d0ee16e3c10a4f8cf0ef6152840828152e68f2f8e3046e89567f2b49ad"}, - "spitfire": {:hex, :spitfire, "0.1.5", "10b041e781bff9544d2fdf00893e1a325758408c5366a9bfa4333072568659b1", [:mix], [], "hexpm", "866a55d21fe827934ff38200111335c9dd311df13cbf2580ed71d84b0a783150"}, + "spark": {:hex, :spark, "2.2.46", "39a1e6b793a754f6a23a1cf12911006125fae0b233250400ad6157991669642d", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "39e4ebfd5bac0c0090e548301680481ed41c1886b0874873363ec22de2bb8a61"}, + "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"}, @@ -55,4 +55,5 @@ "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, + "ymlr": {:hex, :ymlr, "5.1.3", "a8061add5a378e20272a31905be70209a5680fdbe0ad51f40cb1af4bdd0a010b", [:mix], [], "hexpm", "8663444fa85101a117887c170204d4c5a2182567e5f84767f0071cf15f2efb1e"}, } From b03bc135fa582d4bb96531ba42d054340bbcaab0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 06:26:46 -0400 Subject: [PATCH 0961/1215] chore(deps-dev): bump ex_doc in the dev-dependencies group (#509) Bumps the dev-dependencies group with 1 update: [ex_doc](https://github.com/elixir-lang/ex_doc). Updates `ex_doc` from 0.37.2 to 0.37.3 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.2...v0.37.3) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 6195ddff..e31f301a 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,7 @@ "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, @@ -16,7 +16,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"}, + "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, From 95aec5eed01689e922b4b9fa16cfd152f29c2760 Mon Sep 17 00:00:00 2001 From: Theron Boerner Date: Sat, 15 Mar 2025 21:44:06 -0500 Subject: [PATCH 0962/1215] docs: Add docs for read replicas (#511) --- README.md | 1 + .../topics/advanced/using-multiple-repos.md | 56 +++++++++++++++++++ mix.exs | 1 + 3 files changed, 58 insertions(+) create mode 100644 documentation/topics/advanced/using-multiple-repos.md diff --git a/README.md b/README.md index da0136ae..51f74e61 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https:// - [Expressions](documentation/topics/advanced/expressions.md) - [Manual Relationships](documentation/topics/advanced/manual-relationships.md) - [Schema Based Multitenancy](documentation/topics/advanced/schema-based-multitenancy.md) +- [Read Replicas](documentation/topics/advanced/using-multiple-repos.md) ## Reference diff --git a/documentation/topics/advanced/using-multiple-repos.md b/documentation/topics/advanced/using-multiple-repos.md new file mode 100644 index 00000000..3c85ff5c --- /dev/null +++ b/documentation/topics/advanced/using-multiple-repos.md @@ -0,0 +1,56 @@ +# Using Multiple Repos + +When scaling PostgreSQL you may want to setup _read_ replicas to improve +performance and availability. This can be achieved by configuring multiple +repositories in your application. + +## Setup Read Replicas + +Following the [ecto docs](https://hexdocs.pm/ecto/replicas-and-dynamic-repositories.html), change your Repo configuration: + +```elixir +defmodule MyApp.Repo do + use Ecto.Repo, + otp_app: :my_app, + adapter: Ecto.Adapters.Postgres + + @replicas [ + MyApp.Repo.Replica1, + MyApp.Repo.Replica2, + MyApp.Repo.Replica3, + MyApp.Repo.Replica4 + ] + + def replica do + Enum.random(@replicas) + end + + for repo <- @replicas do + defmodule repo do + use Ecto.Repo, + otp_app: :my_app, + adapter: Ecto.Adapters.Postgres, + read_only: true + end + end +end +``` + +## Configure AshPostgres + +Now change the `repo` argument for your `postgres` block as such: + +```elixir +defmodule MyApp.MyDomain.MyResource do + use Ash.Resource, + date_layer: AshPostgres.DataLayer + + postgres do + table "my_resources" + repo fn + _resource, :read -> MyApp.Repo.replica() + _resource, :mutate -> MyApp.Repo + end + end +end +``` diff --git a/mix.exs b/mix.exs index 0eb1a171..232ce1cb 100644 --- a/mix.exs +++ b/mix.exs @@ -97,6 +97,7 @@ defmodule AshPostgres.MixProject do "documentation/topics/development/upgrading-to-2.0.md", "documentation/topics/advanced/expressions.md", "documentation/topics/advanced/schema-based-multitenancy.md", + "documentation/topics/advanced/using-multiple-repos.md", "documentation/topics/advanced/manual-relationships.md", {"documentation/dsls/DSL-AshPostgres.DataLayer.md", search_data: Spark.Docs.search_data_for(AshPostgres.DataLayer)}, From 9c8987aba061058b45b8701046b14b37bde104ad Mon Sep 17 00:00:00 2001 From: quartz Date: Fri, 14 Mar 2025 19:25:39 +0100 Subject: [PATCH 0963/1215] test: custom types in joins (#510) --- .../test_repo/points/20250313112823.json | 47 ++ .../test_repo/posts/20250313112823.json | 580 ++++++++++++++++++ .../string_points/20250313112823.json | 44 ++ .../20250313112823_migrate_resources51.exs | 67 ++ test/support/domain.ex | 2 + test/support/resources/db_point.ex | 34 + test/support/resources/db_string_point.ex | 34 + test/support/resources/post.ex | 25 + test/support/types/string_point.ex | 60 ++ test/type_test.exs | 49 ++ 10 files changed, 942 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/points/20250313112823.json create mode 100644 priv/resource_snapshots/test_repo/posts/20250313112823.json create mode 100644 priv/resource_snapshots/test_repo/string_points/20250313112823.json create mode 100644 priv/test_repo/migrations/20250313112823_migrate_resources51.exs create mode 100644 test/support/resources/db_point.ex create mode 100644 test/support/resources/db_string_point.ex create mode 100644 test/support/types/string_point.ex diff --git a/priv/resource_snapshots/test_repo/points/20250313112823.json b/priv/resource_snapshots/test_repo/points/20250313112823.json new file mode 100644 index 00000000..83a5bc87 --- /dev/null +++ b/priv/resource_snapshots/test_repo/points/20250313112823.json @@ -0,0 +1,47 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": [ + "array", + "float" + ] + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "1E2378D30CF657B673E7EE140FDD4CA067E23FB8862A8F05D35A3F680EBA06B4", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "points_id_index", + "keys": [ + { + "type": "atom", + "value": "id" + } + ], + "name": "id", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "points" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/posts/20250313112823.json b/priv/resource_snapshots/test_repo/posts/20250313112823.json new file mode 100644 index 00000000..cd1bb56d --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250313112823.json @@ -0,0 +1,580 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "metadata", + "type": "map" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "string_point", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "points" + }, + "size": null, + "source": "db_point_id", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_string_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "string_points" + }, + "size": null, + "source": "db_string_point_id", + "type": "text" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "8254859F1DED84E83F9565AB104579D5DEF7B9C756EFC3A2F156CC614B87164F", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/string_points/20250313112823.json b/priv/resource_snapshots/test_repo/string_points/20250313112823.json new file mode 100644 index 00000000..cfdf889c --- /dev/null +++ b/priv/resource_snapshots/test_repo/string_points/20250313112823.json @@ -0,0 +1,44 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "43DADF8B94BBDBD802AE3D70ED67791CC968478B7EE6AEBE5B5F8F676DB323BE", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "string_points_id_index", + "keys": [ + { + "type": "atom", + "value": "id" + } + ], + "name": "id", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "string_points" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250313112823_migrate_resources51.exs b/priv/test_repo/migrations/20250313112823_migrate_resources51.exs new file mode 100644 index 00000000..fe51caf5 --- /dev/null +++ b/priv/test_repo/migrations/20250313112823_migrate_resources51.exs @@ -0,0 +1,67 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources51 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 + create table(:string_points, primary_key: false) do + add(:id, :text, null: false, primary_key: true) + end + + create(unique_index(:string_points, [:id], name: "string_points_id_index")) + + create table(:points, primary_key: false) do + add(:id, {:array, :float}, null: false, primary_key: true) + end + + create(unique_index(:points, [:id], name: "points_id_index")) + + alter table(:posts) do + add(:string_point, :text) + + add( + :db_point_id, + references(:points, + column: :id, + name: "posts_db_point_id_fkey", + type: {:array, :float}, + prefix: "public" + ) + ) + + add( + :db_string_point_id, + references(:string_points, + column: :id, + name: "posts_db_string_point_id_fkey", + type: :text, + prefix: "public" + ) + ) + end + end + + def down do + drop(constraint(:posts, "posts_db_point_id_fkey")) + + drop(constraint(:posts, "posts_db_string_point_id_fkey")) + + alter table(:posts) do + remove(:db_string_point_id) + remove(:db_point_id) + remove(:string_point) + end + + drop_if_exists(unique_index(:points, [:id], name: "points_id_index")) + + drop(table(:points)) + + drop_if_exists(unique_index(:string_points, [:id], name: "string_points_id_index")) + + drop(table(:string_points)) + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index c2d867a7..8af874e4 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -32,6 +32,8 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.PostFollower) resource(AshPostgres.Test.StatefulPostFollower) resource(AshPostgres.Test.PostWithEmptyUpdate) + resource(AshPostgres.Test.DbPoint) + resource(AshPostgres.Test.DbStringPoint) end authorization do diff --git a/test/support/resources/db_point.ex b/test/support/resources/db_point.ex new file mode 100644 index 00000000..25b9ca93 --- /dev/null +++ b/test/support/resources/db_point.ex @@ -0,0 +1,34 @@ +defmodule AshPostgres.Test.DbPoint do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("points") + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:read, :destroy]) + + create :create do + primary?(true) + accept([:id]) + upsert?(true) + upsert_identity(:id) + end + end + + attributes do + attribute(:id, AshPostgres.Test.Point) do + public?(true) + primary_key?(true) + allow_nil?(false) + end + end + + identities do + identity(:id, [:id]) + end +end diff --git a/test/support/resources/db_string_point.ex b/test/support/resources/db_string_point.ex new file mode 100644 index 00000000..8908527b --- /dev/null +++ b/test/support/resources/db_string_point.ex @@ -0,0 +1,34 @@ +defmodule AshPostgres.Test.DbStringPoint do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("string_points") + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:read, :destroy]) + + create :create do + primary?(true) + accept([:id]) + upsert?(true) + upsert_identity(:id) + end + end + + attributes do + attribute(:id, AshPostgres.Test.StringPoint) do + public?(true) + primary_key?(true) + allow_nil?(false) + end + end + + identities do + identity(:id, [:id]) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 9fb97cdd..098d0ccf 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -456,6 +456,7 @@ defmodule AshPostgres.Test.Post do attribute(:point, AshPostgres.Test.Point, public?: true) attribute(:composite_point, AshPostgres.Test.CompositePoint, public?: true) + attribute(:string_point, AshPostgres.Test.StringPoint, public?: true) attribute(:stuff, :map, public?: true) attribute(:list_of_stuff, {:array, :map}, public?: true) attribute(:uniq_one, :string, public?: true) @@ -541,6 +542,18 @@ defmodule AshPostgres.Test.Post do filter(expr(parent(title) == title and parent(id) != id)) end + has_many :posts_with_matching_point, __MODULE__ do + public?(true) + no_attributes?(true) + filter(expr(parent(point) == point and parent(id) != id)) + end + + has_many :posts_with_matching_string_point, __MODULE__ do + public?(true) + no_attributes?(true) + filter(expr(parent(string_point) == string_point and parent(id) != id)) + end + has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id, public?: true) has_one :latest_comment, AshPostgres.Test.Comment do @@ -658,6 +671,18 @@ defmodule AshPostgres.Test.Post do end has_many(:permalinks, AshPostgres.Test.Permalink) + + belongs_to :db_point, AshPostgres.Test.DbPoint do + public?(true) + allow_nil?(true) + attribute_type(AshPostgres.Test.Point) + end + + belongs_to :db_string_point, AshPostgres.Test.DbStringPoint do + public?(true) + allow_nil?(true) + attribute_type(AshPostgres.Test.StringPoint) + end end validations do diff --git a/test/support/types/string_point.ex b/test/support/types/string_point.ex new file mode 100644 index 00000000..0550a904 --- /dev/null +++ b/test/support/types/string_point.ex @@ -0,0 +1,60 @@ +defmodule AshPostgres.Test.StringPoint do + @moduledoc false + use Ash.Type + + defstruct [:x, :y, :z] + + @type t :: %__MODULE__{ + x: float(), + y: float(), + z: float() + } + + def storage_type(_), do: :string + + def cast_input(nil, _), do: {:ok, nil} + + def cast_input(%__MODULE__{} = a, _) do + {:ok, a} + end + + def cast_input({x, y, z}, _) when is_float(x) and is_float(y) and is_float(z) do + {:ok, %__MODULE__{x: x, y: y, z: z}} + end + + def cast_input(enc, _) when is_binary(enc) do + {:ok, parse!(enc)} + end + + def cast_input(_, _), do: :error + + def cast_stored(nil, _), do: {:ok, nil} + + def cast_stored(enc, _) when is_binary(enc) do + {:ok, parse!(enc)} + end + + def cast_stored(_, _) do + :error + end + + def dump_to_native(nil, _), do: {:ok, nil} + + def dump_to_native(%__MODULE__{x: x, y: y, z: z}, _) do + enc = Enum.map_join([x, y, z], ",", &Float.to_string/1) + + {:ok, enc} + end + + def dump_to_native(_, _) do + :error + end + + defp parse!(enc) when is_binary(enc) do + [x, y, z] = + String.split(enc, ",") + |> Enum.map(&String.to_float/1) + + %__MODULE__{x: x, y: y, z: z} + end +end diff --git a/test/type_test.exs b/test/type_test.exs index 856a2da3..428fcab1 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -51,4 +51,53 @@ defmodule AshPostgres.Test.TypeTest do |> Ash.Query.filter(point == ^{1.0, 2.0, 3.0}) |> Ash.read!() end + + test "complex custom types can be used in relationships" do + [p | _] = + for _ <- 1..4//1 do + Post + |> Ash.Changeset.for_create(:create, %{ + point: {1.0, 2.0, 3.0}, + string_point: "1.0,2.0,3.0" + }) + |> Ash.create!() + end + + p = p |> Ash.load!([:posts_with_matching_point, :posts_with_matching_string_point]) + + assert Enum.count(p.posts_with_matching_point) == 3 + assert Enum.count(p.posts_with_matching_string_point) == 3 + + %{id: id} = + Post + |> Ash.Changeset.for_create(:create) + |> Ash.Changeset.manage_relationship(:db_point, %{id: {2.0, 3.0, 4.0}}, type: :create) + |> Ash.create!() + + [p] = + Post + |> Ash.Query.for_read(:read) + |> Ash.Query.load(:db_point) + |> Ash.Query.filter(id == ^id) + |> Ash.read!() + + assert p.db_point_id == {2.0, 3.0, 4.0} + assert p.db_point.id == {2.0, 3.0, 4.0} + + %{id: id} = + Post + |> Ash.Changeset.for_create(:create) + |> Ash.Changeset.manage_relationship(:db_string_point, %{id: "2.0,3.0,4.0"}, type: :create) + |> Ash.create!() + + [p] = + Post + |> Ash.Query.for_read(:read) + |> Ash.Query.load(:db_string_point) + |> Ash.Query.filter(id == ^id) + |> Ash.read!() + + assert %{x: 2.0, y: 3.0, z: 4.0} = p.db_string_point_id + assert %{x: 2.0, y: 3.0, z: 4.0} = p.db_string_point.id + end end From 487d7acd1a71877d8d2a4a0fe8fa2adfdf431437 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 16 Mar 2025 01:54:16 -0400 Subject: [PATCH 0964/1215] improvement: include error detail in constraint violation errors --- lib/data_layer.ex | 51 +++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d09c88e2..54022305 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2187,14 +2187,6 @@ defmodule AshPostgres.DataLayer do end) end - defp to_ash_error({field, {message, vars}}) do - Ash.Error.Changes.InvalidAttribute.exception( - field: field, - message: message, - private_vars: vars - ) - end - defp ecto_changeset(record, changeset, type, repo, table_error?) do attributes = changeset.resource @@ -2332,7 +2324,7 @@ defmodule AshPostgres.DataLayer do constraints -> {:error, fake_changeset - |> constraints_to_errors(:insert, constraints, resource) + |> constraints_to_errors(:insert, constraints, resource, error) |> Ash.Error.to_ash_error()} end end @@ -2349,35 +2341,18 @@ defmodule AshPostgres.DataLayer do defp handle_raised_error( %Postgrex.Error{} = error, stacktrace, - %{constraints: user_constraints}, - _resource + changeset, + resource ) do case Ecto.Adapters.Postgres.Connection.to_constraints(error, []) do - [{type, constraint}] -> - user_constraint = - Enum.find(user_constraints, fn c -> - case {c.type, c.constraint, c.match} do - {^type, ^constraint, :exact} -> true - {^type, cc, :suffix} -> String.ends_with?(constraint, cc) - {^type, cc, :prefix} -> String.starts_with?(constraint, cc) - {^type, %Regex{} = r, _match} -> Regex.match?(r, constraint) - _ -> false - end - end) - - case user_constraint do - %{field: field, error_message: error_message, error_type: error_type} -> - {:error, - to_ash_error( - {field, {error_message, [constraint: error_type, constraint_name: constraint]}} - )} - - nil -> - reraise error, stacktrace - end + [] -> + {:error, Ash.Error.to_ash_error(error, stacktrace)} - _ -> - reraise error, stacktrace + constraints -> + {:error, + changeset + |> constraints_to_errors(:insert, constraints, resource, error) + |> Ash.Error.to_ash_error()} end end @@ -2389,7 +2364,8 @@ defmodule AshPostgres.DataLayer do %{constraints: user_constraints} = changeset, action, constraints, - resource + resource, + error ) do Enum.map(constraints, fn {type, constraint} -> user_constraint = @@ -2421,7 +2397,8 @@ defmodule AshPostgres.DataLayer do message: error_message, private_vars: [ constraint: constraint, - constraint_type: type + constraint_type: type, + detail: error.postgres.detail ] ) end) From bfd98a79dbcc2a02124278a89a2fcec1568c914f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 17 Mar 2025 16:54:30 -0400 Subject: [PATCH 0965/1215] test: test for recent ash_sql aggregate fix --- test/aggregate_test.exs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 0bb25e81..dc784612 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -20,6 +20,37 @@ defmodule AshSql.AggregateTest do assert Ash.count!(AshPostgres.Test.PostView) == 0 end + test "can sum count aggregates" do + org = + Organization + |> Ash.Changeset.for_create(:create, %{name: "The Org"}) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert Decimal.eq?(Ash.sum!(Post, :count_of_comments), Decimal.new("2")) + end + test "relates to actor via has_many and with an aggregate" do org = Organization From 3e91cfb2c32c784bb03474a41c28332a0ba50845 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Mar 2025 13:00:44 -0400 Subject: [PATCH 0966/1215] test: test for present validation --- test/support/resources/post.ex | 11 +++++++++++ test/update_test.exs | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 098d0ccf..602007aa 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -159,6 +159,17 @@ defmodule AshPostgres.Test.Post do change(atomic_update(:limited_score, expr((limited_score || 0) + ^arg(:amount)))) end + update :validate_absent_non_atomically do + require_atomic?(false) + accept([:title]) + validate(absent(:title)) + end + + update :validate_absent do + accept([:title]) + validate(absent(:title)) + end + update :change_nothing do accept([]) require_atomic?(false) diff --git a/test/update_test.exs b/test/update_test.exs index cf93b0f1..eb656490 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -76,6 +76,29 @@ defmodule AshPostgres.UpdateTest do end end + test "absent validations behave the same atomically and non-atomically" do + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "match"}) + |> Ash.create!() + + assert_raise Ash.Error.Invalid, ~r/must be absent/, fn -> + Ash.update!(post, %{title: "title"}, action: :validate_absent_non_atomically) + end + + assert_raise Ash.Error.Invalid, ~r/must be absent/, fn -> + Ash.update!(post, %{}, action: :validate_absent_non_atomically) + end + + assert_raise Ash.Error.Invalid, ~r/must be absent/, fn -> + Ash.update!(post, %{title: "title"}, action: :validate_absent) + end + + assert_raise Ash.Error.Invalid, ~r/must be absent/, fn -> + Ash.update!(post, %{}, action: :validate_absent) + end + end + test "timestamps arent updated if there are no changes non-atomically" do post = AshPostgres.Test.Post From 68fc923a78fa296fd7ae6b56b8be5872b737387b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Mar 2025 16:44:45 -0400 Subject: [PATCH 0967/1215] chore: fix dialyzer and update deps --- lib/resource_generator/spec.ex | 1 - mix.exs | 4 ++-- mix.lock | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 6e3d17dc..12535bbb 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -883,7 +883,6 @@ defmodule AshPostgres.ResourceGenerator.Spec do case get_type(attribute, opts) do :skip -> [] {:ok, type} -> [%{attribute | attr_type: type}] - :error -> [] end end diff --git a/mix.exs b/mix.exs index 232ce1cb..1a1b5b6a 100644 --- a/mix.exs +++ b/mix.exs @@ -166,8 +166,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.65")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.57")}, + {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.62")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index e31f301a..0d392deb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.68", "d6478880cf11500d4cc15a23c73ef78dc0471a4c59891ec925124a0922df2746", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bb8249b45a48de04664fa73ab2cbbc3ba56b792ced4997bdc6862c39f3fe47b4"}, - "ash_sql": {:hex, :ash_sql, "0.2.61", "ef6de767e67613b6b1cf39da525cf72c3581f367506c38624e6a8f4506192d3c", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "8555d1e68c3fe7408cd3a5383e5a881b55204ce0c95cddf18dddf8c0fe7e9ca2"}, + "ash": {:hex, :ash, "3.4.69", "7edc8c91b228e133cf6669026a769862387cbc105bf22b5fc76d7e3a2211cb7d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7b33d0d16c9d18d09978c43eb43996326a83f010166bf374a4d84553b2b554b1"}, + "ash_sql": {:hex, :ash_sql, "0.2.62", "fcf1dde5a453cb024799bd43ab25aee3a7cc4ce7a48f1456310a65aec9e7ea7a", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "df8c72b9b1c7b2c3147334eb63e819bc8d15288e1c6f0ddcd7691530db272ce0"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.35", "ebc02429d7ea80b44c861018051f2cd6ca5e45297ed1e5041ee3ed82f39ffd92", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "0d0dc13cc22636649d9e5dfd7ac13207f179523673fc0d097aa9ea98e29189c2"}, + "igniter": {:hex, :igniter, "0.5.37", "e677c009fcad535759dfb8fec0214af38acf1f27b7d6d0277d6be61d6bb391e8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ca4e5f7c1edead2c6b8955886b010878660c69f3a3f97bf46641b3c39ed339ce"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -40,7 +40,7 @@ "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "reactor": {:hex, :reactor, "0.15.0", "556937d9310e1a6dd06083592b9eb9e0d212540b6d82faecba70823ee7a0747d", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f634383a7760ba3106d31a3185f2e2c39e1485d899d884d94c22c62c9b5e7a4a"}, - "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, + "req": {:hex, :req, "0.5.9", "09072dcd91a70c58734c4dd4fa878a9b6d36527291152885100ec33a5a07f1d6", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2f027043003275918f5e79e6a4e57b10cb17161a1ab41c959aa40ecfb2142e5a"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, From ce7207689d3aed1a6d901c7fb30484ab38853383 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 18 Mar 2025 16:45:04 -0400 Subject: [PATCH 0968/1215] chore: release version v2.5.12 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b241858..3839c5d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.12](https://github.com/ash-project/ash_postgres/compare/v2.5.11...v2.5.12) (2025-03-18) + + + + +### Improvements: + +* include error detail in constraint violation errors + ## [v2.5.11](https://github.com/ash-project/ash_postgres/compare/v2.5.10...v2.5.11) (2025-03-11) diff --git a/mix.exs b/mix.exs index 1a1b5b6a..b1d53a58 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.11" + @version "2.5.12" def project do [ From 76c3ce1ae6cbb230270266817beb4bfa345616fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Mar 2025 13:46:40 -0400 Subject: [PATCH 0969/1215] chore(deps): bump ash in the production-dependencies group (#512) Bumps the production-dependencies group with 1 update: [ash](https://github.com/ash-project/ash). Updates `ash` from 3.4.69 to 3.4.70 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.4.69...v3.4.70) --- updated-dependencies: - dependency-name: ash dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 0d392deb..6bb05593 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.69", "7edc8c91b228e133cf6669026a769862387cbc105bf22b5fc76d7e3a2211cb7d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7b33d0d16c9d18d09978c43eb43996326a83f010166bf374a4d84553b2b554b1"}, + "ash": {:hex, :ash, "3.4.70", "3c104de892a2e31ffb7fe8adf96c10759466d0bdeda7adff8907bf09e56fcca0", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e006e8c88c0eb3cdae3b6c15764c03290820429e1a4b40f700767ce689eb671"}, "ash_sql": {:hex, :ash_sql, "0.2.62", "fcf1dde5a453cb024799bd43ab25aee3a7cc4ce7a48f1456310a65aec9e7ea7a", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "df8c72b9b1c7b2c3147334eb63e819bc8d15288e1c6f0ddcd7691530db272ce0"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From f3fb42c9bd0d4d287b32542260247080a807b582 Mon Sep 17 00:00:00 2001 From: Diogo Martins <39999027+diogomrts@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:19:57 +0000 Subject: [PATCH 0970/1215] test: test for bulk update jsonb attribute errors (#513) --- .../test_repo/csv/20250320225052.json | 55 ++++++++++ .../test_repo/users/20250320225052.json | 101 ++++++++++++++++++ .../20250320225052_add_csv_resource.exs | 42 ++++++++ test/bulk_update_test.exs | 20 +++- test/support/domain.ex | 1 + test/support/resources/csv.ex | 56 ++++++++++ 6 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/csv/20250320225052.json create mode 100644 priv/resource_snapshots/test_repo/users/20250320225052.json create mode 100644 priv/test_repo/migrations/20250320225052_add_csv_resource.exs create mode 100644 test/support/resources/csv.ex diff --git a/priv/resource_snapshots/test_repo/csv/20250320225052.json b/priv/resource_snapshots/test_repo/csv/20250320225052.json new file mode 100644 index 00000000..122327c5 --- /dev/null +++ b/priv/resource_snapshots/test_repo/csv/20250320225052.json @@ -0,0 +1,55 @@ +{ + "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": "column_mapping_embedded", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "column_mapping_new_type", + "type": [ + "array", + "map" + ] + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "B336E8AA99A8DCB5D4FB2965D41E33F6E4F172A10CE24F5DA5E4A5912DD5C87E", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "csv" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/users/20250320225052.json b/priv/resource_snapshots/test_repo/users/20250320225052.json new file mode 100644 index 00000000..b52adc81 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20250320225052.json @@ -0,0 +1,101 @@ +{ + "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": "is_active", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": false, + "default": "\"user\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": false, + "default": "[]", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role_list", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "users_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "C7223D0C16D166EC2260C9B1125B9EDE6D38AD22B95A2280F71BD3069EC205C4", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "users" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250320225052_add_csv_resource.exs b/priv/test_repo/migrations/20250320225052_add_csv_resource.exs new file mode 100644 index 00000000..b02499d1 --- /dev/null +++ b/priv/test_repo/migrations/20250320225052_add_csv_resource.exs @@ -0,0 +1,42 @@ +defmodule AshPostgres.TestRepo.Migrations.AddCsvResource 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(:users) do + remove(:org_id) + modify(:role_list, {:array, :text}, null: false) + modify(:role, :text, null: false) + end + + create table(:csv, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:column_mapping_embedded, :jsonb) + add(:column_mapping_new_type, :jsonb) + end + end + + def down do + drop(table(:csv)) + + alter table(:users) do + modify(:role, :text, null: true) + modify(:role_list, {:array, :text}, null: true) + + add( + :org_id, + references(:multitenant_orgs, + column: :id, + name: "users_org_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + end +end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 201595a7..242ba7f2 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.BulkUpdateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Post, Record} + alias AshPostgres.Test.{Post, Record, CSV} require Ash.Expr require Ash.Query @@ -263,4 +263,22 @@ defmodule AshPostgres.BulkUpdateTest do authorize?: false ) end + + @tag :wip + test "jsonb[] attribute with embedded Resource definition can be created and updated" do + %{status: :success} = + Ash.bulk_create!( + [%{column_mapping_embedded: [%{column: 1, attribute: "foo"}]}], + CSV, + :create, + return_records?: true, + return_errors?: true + ) + + %{status: :success} = + Ash.bulk_update(CSV, :update, %{ + column_mapping_embedded: [%{column: 1, attribute: "foo"}], + column_mapping_new_type: [%{column: 1, attribute: "foo"}] + }) + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index 8af874e4..36cdab5c 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -34,6 +34,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.PostWithEmptyUpdate) resource(AshPostgres.Test.DbPoint) resource(AshPostgres.Test.DbStringPoint) + resource(AshPostgres.Test.CSV) end authorization do diff --git a/test/support/resources/csv.ex b/test/support/resources/csv.ex new file mode 100644 index 00000000..59026cba --- /dev/null +++ b/test/support/resources/csv.ex @@ -0,0 +1,56 @@ +defmodule AshPostgres.Test.CSVColumnMatchingEmbedded do + use Ash.Resource, + data_layer: :embedded + + attributes do + attribute(:column, :integer, allow_nil?: true, public?: true) + attribute(:attribute, :string, allow_nil?: true, public?: true) + end +end + +defmodule AshPostgres.Test.CSVColumnMappingNewType do + use Ash.Type.NewType, + subtype_of: :map, + constraints: [ + fields: [ + attribute: [type: :string, allow_nil?: false], + column: [type: :integer, allow_nil?: false] + ] + ] +end + +defmodule AshPostgres.Test.CSV do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + alias AshPostgres.Test + + actions do + default_accept(:*) + + defaults([:create, :read, :destroy]) + + update :update do + primary?(true) + accept([:column_mapping_embedded, :column_mapping_new_type]) + # require_atomic?(false) + end + end + + attributes do + uuid_primary_key(:id) + + attribute(:column_mapping_embedded, {:array, Test.CSVColumnMatchingEmbedded}, public?: true) + attribute(:column_mapping_new_type, {:array, Test.CSVColumnMatchingEmbedded}, public?: true) + end + + postgres do + table "csv" + repo(AshPostgres.TestRepo) + + storage_types column_mapping_embedded: :jsonb + storage_types column_mapping_new_type: :jsonb + end +end From e1adfacfa7a086dc03d97fac1f1d430a9bcafea2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 10:25:09 -0400 Subject: [PATCH 0971/1215] test: add test for loading aggs on aggregate --- test/aggregate_test.exs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index dc784612..d3aeec87 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -128,6 +128,32 @@ defmodule AshSql.AggregateTest do describe "Context Multitenancy" do alias AshPostgres.MultitenancyTest.{Org, Post, User} + test "aggregating with a filter on an aggregate honors the tenant" do + org = + Org + |> Ash.Changeset.for_create(:create, %{name: "BTTF"}) + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{name: "Marty", org_id: org.id}) + |> Ash.create!() + + ["Back to 1955", "Forwards to 1985", "Forward to 2015", "Back again to 1985"] + |> Enum.map( + &(Post + |> Ash.Changeset.for_create(:create, %{name: &1, user_id: user.id}) + |> Ash.create!(tenant: "org_#{org.id}", load: [:last_word])) + ) + + assert 1 == + User + |> Ash.Query.set_tenant("org_#{org.id}") + |> Ash.Query.filter(count_visited > 1) + |> Ash.Query.load(:count_visited) + |> Ash.count!() + end + test "loading a nested aggregate honors tenant" do alias AshPostgres.MultitenancyTest.{Org, Post, User} From c228e0bcbd2bbe482353e3ac3416c9f428714423 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 14:17:01 -0400 Subject: [PATCH 0972/1215] test: fix tests & update tag/regression test --- .../test_repo/users/20250321142835.json | 130 ++++++++++++++++++ .../20250321142835_migrate_resources52.exs | 36 +++++ test/bulk_update_test.exs | 11 +- test/support/resources/csv.ex | 3 +- 4 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/users/20250321142835.json create mode 100644 priv/test_repo/migrations/20250321142835_migrate_resources52.exs diff --git a/priv/resource_snapshots/test_repo/users/20250321142835.json b/priv/resource_snapshots/test_repo/users/20250321142835.json new file mode 100644 index 00000000..b1842600 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20250321142835.json @@ -0,0 +1,130 @@ +{ + "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": "is_active", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "\"user\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": true, + "default": "[]", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role_list", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "users_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "name": "users_org_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_orgs" + }, + "size": null, + "source": "org_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "8DC96004D53169EA4AA3721AD7836DB2A230F30C55EE1B06DDB1D7505AE50210", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "users" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250321142835_migrate_resources52.exs b/priv/test_repo/migrations/20250321142835_migrate_resources52.exs new file mode 100644 index 00000000..a347e279 --- /dev/null +++ b/priv/test_repo/migrations/20250321142835_migrate_resources52.exs @@ -0,0 +1,36 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources52 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(:users) do + modify(:role_list, {:array, :text}, null: true) + modify(:role, :text, null: true) + + add( + :org_id, + references(:multitenant_orgs, + column: :id, + name: "users_org_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + end + + def down do + drop(constraint(:users, "users_org_id_fkey")) + + alter table(:users) do + remove(:org_id) + modify(:role, :text, null: false) + modify(:role_list, {:array, :text}, null: false) + end + end +end diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 242ba7f2..5a086645 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -264,7 +264,7 @@ defmodule AshPostgres.BulkUpdateTest do ) end - @tag :wip + @tag :regression test "jsonb[] attribute with embedded Resource definition can be created and updated" do %{status: :success} = Ash.bulk_create!( @@ -275,10 +275,9 @@ defmodule AshPostgres.BulkUpdateTest do return_errors?: true ) - %{status: :success} = - Ash.bulk_update(CSV, :update, %{ - column_mapping_embedded: [%{column: 1, attribute: "foo"}], - column_mapping_new_type: [%{column: 1, attribute: "foo"}] - }) + Ash.bulk_update!(CSV, :update, %{ + column_mapping_embedded: [%{column: 1, attribute: "foo"}], + column_mapping_new_type: [%{column: 1, attribute: "foo"}] + }) end end diff --git a/test/support/resources/csv.ex b/test/support/resources/csv.ex index 59026cba..8a76e703 100644 --- a/test/support/resources/csv.ex +++ b/test/support/resources/csv.ex @@ -50,7 +50,6 @@ defmodule AshPostgres.Test.CSV do table "csv" repo(AshPostgres.TestRepo) - storage_types column_mapping_embedded: :jsonb - storage_types column_mapping_new_type: :jsonb + storage_types column_mapping_embedded: :jsonb, column_mapping_new_type: :jsonb end end From f3c32782b80c4d108f64fdb43a4d5880c18f7ddb Mon Sep 17 00:00:00 2001 From: artiom Date: Fri, 21 Mar 2025 19:04:47 +0000 Subject: [PATCH 0973/1215] fix: order when renaming attribute with an index (#514) --- .../migration_generator.ex | 13 +++ test/migration_generator_test.exs | 81 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 1c35b6b0..73fe1d3d 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1375,6 +1375,19 @@ defmodule AshPostgres.MigrationGenerator do true end + defp after?( + %Operation.AddCustomIndex{ + table: table, + schema: schema + }, + %Operation.RenameAttribute{ + table: table, + schema: schema + } + ) do + true + end + defp after?( %Operation.AddReferenceIndex{ table: table, diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 2e9bd9e9..f36418a0 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -393,6 +393,87 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "custom_indexes with follow up migrations" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + + defposts do + postgres do + custom_indexes do + index([:title]) + end + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + end + + test "it changes attribute and index in the correct order" do + defposts do + postgres do + custom_indexes do + index([:title_short]) + end + end + + attributes do + uuid_primary_key(:id) + attribute(:title_short, :string, public?: true) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + contents = File.read!(file2) + + [up_side, down_side] = String.split(contents, "def down", parts: 2) + + up_side_parts = String.split(up_side, "\n", trim: true) + + assert Enum.find_index(up_side_parts, fn x -> + x == "rename table(:posts), :title, to: :title_short" + end) < + Enum.find_index(up_side_parts, fn x -> + x == "create index(:posts, [:title_short])" + end) + + down_side_parts = String.split(down_side, "\n", trim: true) + + assert Enum.find_index(down_side_parts, fn x -> + x == "rename table(:posts), :title_short, to: :title" + end) < + Enum.find_index(down_side_parts, fn x -> x == "create index(:posts, [:title])" end) + end + end + describe "creating follow up migrations with a composite primary key" do setup do on_exit(fn -> From 33c39d24afd6c7b752d754ec436644b36d1d81eb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 15:05:37 -0400 Subject: [PATCH 0974/1215] chore: fix credo --- test/bulk_update_test.exs | 2 +- test/support/resources/csv.ex | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 5a086645..092f964d 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.BulkUpdateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Post, Record, CSV} + alias AshPostgres.Test.{CSV, Post, Record} require Ash.Expr require Ash.Query diff --git a/test/support/resources/csv.ex b/test/support/resources/csv.ex index 8a76e703..91894ed4 100644 --- a/test/support/resources/csv.ex +++ b/test/support/resources/csv.ex @@ -1,4 +1,5 @@ defmodule AshPostgres.Test.CSVColumnMatchingEmbedded do + @moduledoc false use Ash.Resource, data_layer: :embedded @@ -9,6 +10,7 @@ defmodule AshPostgres.Test.CSVColumnMatchingEmbedded do end defmodule AshPostgres.Test.CSVColumnMappingNewType do + @moduledoc false use Ash.Type.NewType, subtype_of: :map, constraints: [ From 762a283f8c15255a4de5526258b8adc8e4e12f9c Mon Sep 17 00:00:00 2001 From: Oliver Severin Mulelid-Tynes Date: Mon, 24 Mar 2025 15:40:31 +0100 Subject: [PATCH 0975/1215] fix parsing of constraints and indexes that are wrapped in quotes (#515) --- lib/resource_generator/spec.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 12535bbb..d7b83fef 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -397,7 +397,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do |> String.trim_leading("CREATE ") |> String.trim_leading("UNIQUE ") |> String.trim_leading("INDEX ") - |> String.replace(~r/^[a-zA-Z0-9_\.]+\s/, "") + |> String.replace(~r/^"?[a-zA-Z0-9_\.]+"?\s/, "") |> String.trim_leading("ON ") |> String.replace(~r/^[\S]+/, "") |> String.trim_leading() From 14302a4e308d3065ddca359ddaf6de39e956374d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 24 Mar 2025 12:39:54 -0400 Subject: [PATCH 0976/1215] chore: use atomic --- mix.lock | 8 ++++---- test/atomics_test.exs | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mix.lock b/mix.lock index 6bb05593..8d6a54cc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.4.70", "3c104de892a2e31ffb7fe8adf96c10759466d0bdeda7adff8907bf09e56fcca0", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e006e8c88c0eb3cdae3b6c15764c03290820429e1a4b40f700767ce689eb671"}, + "ash": {:hex, :ash, "3.4.71", "ce8fa3c38bb59d067647bdc87aa9198335fdeeab36660c869b72c47339fc9d69", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f255da731b5b3ec4d916b5282faecbbbe9beb64a3641b4a45ac91160ffea3cc9"}, "ash_sql": {:hex, :ash_sql, "0.2.62", "fcf1dde5a453cb024799bd43ab25aee3a7cc4ce7a48f1456310a65aec9e7ea7a", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "df8c72b9b1c7b2c3147334eb63e819bc8d15288e1c6f0ddcd7691530db272ce0"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.37", "e677c009fcad535759dfb8fec0214af38acf1f27b7d6d0277d6be61d6bb391e8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ca4e5f7c1edead2c6b8955886b010878660c69f3a3f97bf46641b3c39ed339ce"}, + "igniter": {:hex, :igniter, "0.5.38", "436a6414abc9245e539d6c92a6f4854f62270fbf6547c4acf1e5a65c0e4f4d4b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "e5f1474a2a7ad186f3b71074d9d1ef25d306634e12af4ea01beae06ba958491d"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -40,12 +40,12 @@ "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "reactor": {:hex, :reactor, "0.15.0", "556937d9310e1a6dd06083592b9eb9e0d212540b6d82faecba70823ee7a0747d", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f634383a7760ba3106d31a3185f2e2c39e1485d899d884d94c22c62c9b5e7a4a"}, - "req": {:hex, :req, "0.5.9", "09072dcd91a70c58734c4dd4fa878a9b6d36527291152885100ec33a5a07f1d6", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2f027043003275918f5e79e6a4e57b10cb17161a1ab41c959aa40ecfb2142e5a"}, + "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spark": {:hex, :spark, "2.2.46", "39a1e6b793a754f6a23a1cf12911006125fae0b233250400ad6157991669642d", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "39e4ebfd5bac0c0090e548301680481ed41c1886b0874873363ec22de2bb8a61"}, + "spark": {:hex, :spark, "2.2.48", "dd1005c26c7f98ea686a951f7ae58fffb54eff19c47830e6ff68b93f87433baa", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "379912647b9ddcc5265e91a82a235a264a727123d1f9e90052d91ad8cebbb2d0"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 2b77815a..e3e13395 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -118,9 +118,11 @@ defmodule AshPostgres.AtomicsTest do post |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.atomic_update(%{ - list_of_stuff: [ - %{foo: [%{a: 1, b: %{c: [1, 2, expr(type(fragment("3"), :integer))]}}]} - ] + list_of_stuff: + {:atomic, + [ + %{foo: [%{a: 1, b: %{c: [1, 2, expr(type(fragment("3"), :integer))]}}]} + ]} }) |> Ash.update!() end From b830abc4cf53fc3af3dea73ddd73069d634f57b2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Mar 2025 11:17:00 -0400 Subject: [PATCH 0977/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 8d6a54cc..8415722b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.71", "ce8fa3c38bb59d067647bdc87aa9198335fdeeab36660c869b72c47339fc9d69", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f255da731b5b3ec4d916b5282faecbbbe9beb64a3641b4a45ac91160ffea3cc9"}, - "ash_sql": {:hex, :ash_sql, "0.2.62", "fcf1dde5a453cb024799bd43ab25aee3a7cc4ce7a48f1456310a65aec9e7ea7a", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "df8c72b9b1c7b2c3147334eb63e819bc8d15288e1c6f0ddcd7691530db272ce0"}, + "ash": {:hex, :ash, "3.4.72", "34596fcfd91822a774495097ce531d92040b629c69d2eca2626ce35e0fea6bb9", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dce62c275efbfc5e82b35dc5e2a8c655523416079433cf186204f68b39fbc337"}, + "ash_sql": {:hex, :ash_sql, "0.2.63", "1b3c9e9d59d17cccf7b105c96ed15f7236ade502c91432c581bb8ce16507cf29", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "66b99e314ba6fe60bf80a6a4999df50af9ac4053e24b0742225bcd187def44c1"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.38", "436a6414abc9245e539d6c92a6f4854f62270fbf6547c4acf1e5a65c0e4f4d4b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "e5f1474a2a7ad186f3b71074d9d1ef25d306634e12af4ea01beae06ba958491d"}, + "igniter": {:hex, :igniter, "0.5.39", "57b18dff11d929e6f41aba76b3cf92a08393fed2e9a5b1dff5590186572e9040", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "1d962b7062b303e1123cb460b07b6c778bfea4c249c7d4f37fce465ed409ea86"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From b9c38f985d009211548159f603447c07e101b3f4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Mar 2025 11:17:06 -0400 Subject: [PATCH 0978/1215] chore: release version v2.5.13 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3839c5d9..476609be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.13](https://github.com/ash-project/ash_postgres/compare/v2.5.12...v2.5.13) (2025-03-25) + + + + +### Bug Fixes: + +* order when renaming attribute with an index (#514) + ## [v2.5.12](https://github.com/ash-project/ash_postgres/compare/v2.5.11...v2.5.12) (2025-03-18) diff --git a/mix.exs b/mix.exs index b1d53a58..15a15fd5 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.12" + @version "2.5.13" def project do [ From 05f4220c7f4ca9c627bd4d92a081a4c5a466e87d Mon Sep 17 00:00:00 2001 From: Jechol Lee Date: Wed, 26 Mar 2025 10:42:36 +0900 Subject: [PATCH 0979/1215] test: loaded relationships remain loaded after update (#516) --- test/update_test.exs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/update_test.exs b/test/update_test.exs index eb656490..d05f9863 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -218,4 +218,25 @@ defmodule AshPostgres.UpdateTest do assert is_nil(post.author) end + + test "loaded relationships remain loaded after update" do + author = + AshPostgres.Test.Author + |> Ash.Changeset.for_create(:create, %{first_name: "foo", last_name: "bar"}) + |> Ash.create!() + + _post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{title: "baz"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + author = author |> Ash.load!(:posts) + + assert [%Post{}] = author.posts + + author = author |> Ash.Changeset.for_update(:update, %{first_name: "foo2"}) |> Ash.update!() + + assert [%Post{}] = author.posts + end end From 72eeb126286584a995645300cea1dadc8ba87cba Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 25 Mar 2025 22:25:28 -0400 Subject: [PATCH 0980/1215] fix: retain loads on atomic upgrade update actions fixes #517 --- lib/data_layer.ex | 26 +++++++++++++++++--------- test/support/resources/author.ex | 6 +++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 54022305..10e69221 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1512,7 +1512,23 @@ defmodule AshPostgres.DataLayer do end) if options[:return_records?] do - {:ok, AshSql.Query.remap_mapped_fields(results, query)} + results = AshSql.Query.remap_mapped_fields(results, query) + + if changeset.context[:data_layer][:use_atomic_update_data?] && + Enum.count_until(results, 2) == 1 do + modifying = + Map.keys(changeset.attributes) ++ + Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) + + result = hd(results) + + Map.merge(changeset.data, Map.take(result, modifying)) + |> Map.update!(:aggregates, &Map.merge(&1, result.aggregates)) + |> Map.update!(:calculations, &Map.merge(&1, result.calculations)) + |> then(&{:ok, [&1]}) + else + {:ok, results} + end else :ok end @@ -3042,10 +3058,6 @@ defmodule AshPostgres.DataLayer do def update(resource, changeset) do source = resolve_source(resource, changeset) - modifying = - Map.keys(changeset.attributes) ++ - Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) - query = from(row in source, as: ^0) |> AshSql.Bindings.default_bindings( @@ -3075,10 +3087,6 @@ defmodule AshPostgres.DataLayer do )} {:ok, [record]} -> - record = - changeset.data - |> Map.merge(Map.take(record, modifying)) - maybe_update_tenant(resource, changeset, record) {:ok, record} diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index d3beca51..acf9310a 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -35,7 +35,11 @@ defmodule AshPostgres.Test.Author do actions do default_accept(:*) - defaults([:create, :read, :update, :destroy]) + defaults([:create, :read, :destroy]) + + update :update do + primary?(true) + end end relationships do From 5d0d63bb6375636529d541b87a000e042afa1816 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 26 Mar 2025 17:31:37 -0400 Subject: [PATCH 0981/1215] chore: update deps --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 8415722b..a7225498 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.4.72", "34596fcfd91822a774495097ce531d92040b629c69d2eca2626ce35e0fea6bb9", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dce62c275efbfc5e82b35dc5e2a8c655523416079433cf186204f68b39fbc337"}, - "ash_sql": {:hex, :ash_sql, "0.2.63", "1b3c9e9d59d17cccf7b105c96ed15f7236ade502c91432c581bb8ce16507cf29", [:mix], [{:ash, ">= 3.4.65 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "66b99e314ba6fe60bf80a6a4999df50af9ac4053e24b0742225bcd187def44c1"}, + "ash": {:hex, :ash, "3.5.0", "8b2342b1a048bdde5cde7bb2696ba0f90ff754785a25648785e706b5f632ae10", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0c5a9aec7e0f611858bcce933d6677445ae0a63d45cc0c56f02f4aea7fff6cc2"}, + "ash_sql": {:hex, :ash_sql, "0.2.66", "7021417635367802eaeef0efbfe3116ad1c9e5f690d469708ce0bf97b13d7bbf", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a49b9acb0c14090329ffbe1df8e7b9e8713b377448680c7c204a7483c0ee4899"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -22,8 +22,8 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "igniter": {:hex, :igniter, "0.5.39", "57b18dff11d929e6f41aba76b3cf92a08393fed2e9a5b1dff5590186572e9040", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "1d962b7062b303e1123cb460b07b6c778bfea4c249c7d4f37fce465ed409ea86"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "igniter": {:hex, :igniter, "0.5.40", "c5ad8094852173187eeef7dfabd6110df49047ed649d68a549584d9ede0118fc", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "161155d1ed443b60d52c84f01b31130d2156f6ddce581e6c46f496f93de1d804"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 325aa9a65f3d4820b6fb32215e35946fa49ddcd9 Mon Sep 17 00:00:00 2001 From: artiom Date: Thu, 27 Mar 2025 15:13:40 +0000 Subject: [PATCH 0982/1215] improvement: create schema before table creation (#518) --- lib/migration_generator/phase.ex | 10 ++++++---- test/migration_generator_test.exs | 11 +++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/migration_generator/phase.ex b/lib/migration_generator/phase.ex index b1e3e2b3..d743810d 100644 --- a/lib/migration_generator/phase.ex +++ b/lib/migration_generator/phase.ex @@ -13,14 +13,16 @@ defmodule AshPostgres.MigrationGenerator.Phase do Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <> "\nend" else - opts = + {pre_create, opts} = if schema do - ", prefix: \"#{schema}\"" + {"execute(\"CREATE SCHEMA IF NOT EXISTS #{schema}\")" <> "\n\n", + ", prefix: \"#{schema}\""} else - "" + {"", ""} end - "create table(:#{as_atom(table)}, primary_key: false#{opts}) do\n" <> + pre_create <> + "create table(:#{as_atom(table)}, primary_key: false#{opts}) do\n" <> Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <> "\nend" end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index f36418a0..d9efc296 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -243,14 +243,6 @@ defmodule AshPostgres.MigrationGeneratorTest do defdomain([Post]) - {:ok, _} = - Ecto.Adapters.SQL.query( - AshPostgres.TestRepo, - """ - CREATE SCHEMA IF NOT EXISTS example; - """ - ) - AshPostgres.MigrationGenerator.generate(Domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", @@ -272,6 +264,9 @@ defmodule AshPostgres.MigrationGeneratorTest do file_contents = File.read!(file) + # the migration creates the schema + assert file_contents =~ "execute(\"CREATE SCHEMA IF NOT EXISTS example\")" + # the migration creates the table assert file_contents =~ "create table(:posts, primary_key: false, prefix: \"example\") do" From eb72aaef868db1b43c663e14f17136190356d51e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 28 Mar 2025 08:35:23 -0400 Subject: [PATCH 0983/1215] fix: remove debugging code accidentally committed --- lib/resource_generator/spec.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index d7b83fef..a0f91cac 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -898,8 +898,6 @@ defmodule AshPostgres.ResourceGenerator.Spec do if opts[:yes?] || opts[:skip_unknown] do "skip" else - raise "what" - Mix.shell().prompt(""" Unknown type: #{attribute.type}. What should we use as the type? From a72ad7e48c9b8fe98140d8ebe3f0ba11b5d4d44c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 28 Mar 2025 08:36:03 -0400 Subject: [PATCH 0984/1215] chore: release version v2.5.14 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 476609be..31b78a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.14](https://github.com/ash-project/ash_postgres/compare/v2.5.13...v2.5.14) (2025-03-28) + + + + +### Bug Fixes: + +* remove debugging code accidentally committed + +* retain loads on atomic upgrade update actions + +### Improvements: + +* create schema before table creation (#518) + ## [v2.5.13](https://github.com/ash-project/ash_postgres/compare/v2.5.12...v2.5.13) (2025-03-25) diff --git a/mix.exs b/mix.exs index 15a15fd5..9567037f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.13" + @version "2.5.14" def project do [ From 8fd3bfbf282d5d142c6004b538b3dbc4c7b59b4e Mon Sep 17 00:00:00 2001 From: artiom Date: Fri, 28 Mar 2025 13:40:00 +0000 Subject: [PATCH 0985/1215] fix: use schema when changing reference deferrability (#519) --- lib/migration_generator/operation.ex | 30 ++++++++-- test/migration_generator_test.exs | 84 ++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 87cea347..14f7b52b 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -454,16 +454,34 @@ defmodule AshPostgres.MigrationGenerator.Operation do @moduledoc false defstruct [:table, :schema, :references, :direction, no_phase: true] - def up(%{direction: :up, table: table, references: %{name: name, deferrable: true}}) do - "execute(\"ALTER TABLE #{table} alter CONSTRAINT #{name} DEFERRABLE INITIALLY IMMEDIATE\");" + defp prefix_name(name, prefix) do + if prefix do + "#{prefix}.#{name}" + else + name + end + end + + def up(%{ + direction: :up, + schema: schema, + table: table, + references: %{name: name, deferrable: true} + }) do + "execute(\"ALTER TABLE #{prefix_name(table, schema)} ALTER CONSTRAINT #{name} DEFERRABLE INITIALLY IMMEDIATE\");" end - def up(%{direction: :up, table: table, references: %{name: name, deferrable: :initially}}) do - "execute(\"ALTER TABLE #{table} alter CONSTRAINT #{name} DEFERRABLE INITIALLY DEFERRED\");" + def up(%{ + direction: :up, + schema: schema, + table: table, + references: %{name: name, deferrable: :initially} + }) do + "execute(\"ALTER TABLE #{prefix_name(table, schema)} ALTER CONSTRAINT #{name} DEFERRABLE INITIALLY DEFERRED\");" end - def up(%{direction: :up, table: table, references: %{name: name}}) do - "execute(\"ALTER TABLE #{table} alter CONSTRAINT #{name} NOT DEFERRABLE\");" + def up(%{direction: :up, schema: schema, table: table, references: %{name: name}}) do + "execute(\"ALTER TABLE #{prefix_name(table, schema)} ALTER CONSTRAINT #{name} NOT DEFERRABLE\");" end def up(_), do: "" diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index d9efc296..8c613055 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1499,6 +1499,90 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(file) =~ ~S{create index(:posts, [:post_id])} end + test "references with deferrable modifications generate changes with the correct schema" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:key_id, :uuid, allow_nil?: false, public?: true) + attribute(:foobar, :string, public?: true) + end + + postgres do + schema "example" + end + end + + defposts Post2 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, Post) do + public?(true) + end + end + + postgres do + schema "example" + + references do + reference(:post, index?: true, deferrable: :initially) + end + end + end + + defdomain([Post, Post2]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + defposts Post2 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, Post) do + public?(true) + end + end + + postgres do + schema "example" + + references do + reference(:post, index?: true, deferrable: true) + end + end + end + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert file = + "test_migration_path/**/*_migrate_resources*.exs" + |> Path.wildcard() + |> Enum.reject(&String.contains?(&1, "extensions")) + |> Enum.sort() + |> Enum.at(1) + |> File.read!() + + assert file =~ ~S{execute("ALTER TABLE example.posts ALTER CONSTRAINT} + end + test "index generated by index? true also adds column when using attribute multitenancy" do defresource Org, "orgs" do attributes do From ea546a6e278369d2930c6476c93c784d43d30388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Sat, 29 Mar 2025 20:39:08 +0100 Subject: [PATCH 0986/1215] docs: Use proper flag in ash postgres drop example (#520) --- lib/mix/tasks/ash_postgres.drop.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 5180d1d3..06d9441d 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -24,7 +24,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do ## Examples mix ash_postgres.drop - mix ash_postgres.drop -r MyApp.Repo1,MyApp.Repo2 + mix ash_postgres.drop --domains MyApp.Domain1,MyApp.Domain2 ## Command line options From 800900b09311ca2f87321c232a93ca0c082a8c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Sun, 30 Mar 2025 00:20:59 +0100 Subject: [PATCH 0987/1215] improvement: propagate `-r` flag to Ecto (#521) * Propagate r flag to ecto * Make repos helper function return opts repos when provided --- lib/mix/helpers.ex | 126 +++++++++++++++---------- lib/mix/tasks/ash_postgres.create.ex | 7 +- lib/mix/tasks/ash_postgres.drop.ex | 6 +- lib/mix/tasks/ash_postgres.migrate.ex | 6 +- lib/mix/tasks/ash_postgres.rollback.ex | 5 +- 5 files changed, 91 insertions(+), 59 deletions(-) diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 26abfabd..2eb2d40b 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -38,61 +38,81 @@ defmodule AshPostgres.Mix.Helpers do end def repos!(opts, args) do - if opts[:domains] && opts[:domains] != "" do - domains = domains!(opts, args) + cond do + opts[:repo] && opts[:repo] != "" -> + ensure_load(args) - resources = - domains - |> Enum.flat_map(&Ash.Domain.Info.resources/1) - |> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer)) + opts[:repo] + |> Kernel.||("") + |> String.split(",") + |> Enum.flat_map(fn + "" -> + [] + + repo -> + [Module.concat([repo])] + end) + |> Enum.map(fn repo -> + case Code.ensure_compiled(repo) do + {:module, _} -> + repo + + {:error, error} -> + Mix.raise("Could not load #{inspect(repo)}, error: #{inspect(error)}. ") + end + end) + + opts[:domains] && opts[:domains] != "" -> + domains = domains!(opts, args) + + resources = + domains + |> Enum.flat_map(&Ash.Domain.Info.resources/1) + |> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer)) + |> case do + [] -> + raise """ + No resources with `data_layer: AshPostgres.DataLayer` found in the domains #{Enum.map_join(domains, ",", &inspect/1)}. + + Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`. + """ + + resources -> + resources + end + + resources + |> Enum.flat_map( + &[ + AshPostgres.DataLayer.Info.repo(&1, :read), + AshPostgres.DataLayer.Info.repo(&1, :mutate) + ] + ) + |> Enum.uniq() |> case do [] -> raise """ - No resources with `data_layer: AshPostgres.DataLayer` found in the domains #{Enum.map_join(domains, ",", &inspect/1)}. + No repos could be found configured on the resources in the domains: #{Enum.map_join(domains, ",", &inspect/1)} - Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`. - """ + At least one resource must have a repo configured. - resources -> - resources - end + The following resources were found with `data_layer: AshPostgres.DataLayer`: - resources - |> Enum.flat_map( - &[ - AshPostgres.DataLayer.Info.repo(&1, :read), - AshPostgres.DataLayer.Info.repo(&1, :mutate) - ] - ) - |> Enum.uniq() - |> case do - [] -> - raise """ - No repos could be found configured on the resources in the domains: #{Enum.map_join(domains, ",", &inspect/1)} - - At least one resource must have a repo configured. + #{Enum.map_join(resources, "\n", &"* #{inspect(&1)}")} + """ - The following resources were found with `data_layer: AshPostgres.DataLayer`: + repos -> + repos + end - #{Enum.map_join(resources, "\n", &"* #{inspect(&1)}")} - """ + true -> + ensure_load(args) - repos -> - repos - end - else - if Code.ensure_loaded?(Mix.Tasks.App.Config) do - Mix.Task.run("app.config", args) - else - Mix.Task.run("loadpaths", args) - "--no-compile" not in args && Mix.Task.run("compile", args) - end - - Mix.Project.config()[:app] - |> Application.get_env(:ecto_repos, []) - |> Enum.filter(fn repo -> - Spark.implements_behaviour?(repo, AshPostgres.Repo) - end) + Mix.Project.config()[:app] + |> Application.get_env(:ecto_repos, []) + |> Enum.filter(fn repo -> + Spark.implements_behaviour?(repo, AshPostgres.Repo) + end) end end @@ -117,12 +137,7 @@ defmodule AshPostgres.Mix.Helpers do end defp ensure_compiled(domain, args) do - if Code.ensure_loaded?(Mix.Tasks.App.Config) do - Mix.Task.run("app.config", args) - else - Mix.Task.run("loadpaths", args) - "--no-compile" not in args && Mix.Task.run("compile", args) - end + ensure_load(args) case Code.ensure_compiled(domain) do {:module, _} -> @@ -139,6 +154,15 @@ defmodule AshPostgres.Mix.Helpers do end end + defp ensure_load(args) do + if Code.ensure_loaded?(Mix.Tasks.App.Config) do + Mix.Task.run("app.config", args) + else + Mix.Task.run("loadpaths", args) + "--no-compile" not in args && Mix.Task.run("compile", args) + end + end + def tenants(repo, opts) do tenants = repo.all_tenants() diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index e8b67469..bfe117b1 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -7,11 +7,14 @@ defmodule Mix.Tasks.AshPostgres.Create do quiet: :boolean, domains: :string, no_compile: :boolean, - no_deps_check: :boolean + no_deps_check: :boolean, + repo: :string, + r: :string ] @aliases [ - q: :quiet + q: :quiet, + r: :repo ] @moduledoc """ diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 06d9441d..a79a1025 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -6,7 +6,8 @@ defmodule Mix.Tasks.AshPostgres.Drop do @aliases [ f: :force, - q: :quiet + q: :quiet, + r: :repo ] @switches [ @@ -15,7 +16,8 @@ defmodule Mix.Tasks.AshPostgres.Drop do quiet: :boolean, domains: :string, no_compile: :boolean, - no_deps_check: :boolean + no_deps_check: :boolean, + repo: :string ] @moduledoc """ diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 1db3965d..81e4d63f 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -7,7 +7,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do @shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) domains" @aliases [ - n: :step + n: :step, + r: :repo ] @switches [ @@ -26,7 +27,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do no_deps_check: :boolean, migrations_path: :keep, only_tenants: :string, - except_tenants: :string + except_tenants: :string, + repo: :string ] @moduledoc """ diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index afdb3c8d..211dbdff 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -59,9 +59,10 @@ defmodule Mix.Tasks.AshPostgres.Rollback do log_migrations_sql: :boolean, log_migrator_sql: :boolean, only_tenants: :string, - except_tenants: :string + except_tenants: :string, + repo: :string ], - aliases: [n: :step, v: :to] + aliases: [n: :step, v: :to, r: :repo] ) repos = AshPostgres.Mix.Helpers.repos!(opts, args) From a5d69cd8d6fbf7b0bc045c4792caabb62de3e65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Sun, 30 Mar 2025 00:55:08 +0100 Subject: [PATCH 0988/1215] docs: Document r flag in tasks docs (#522) --- lib/mix/tasks/ash_postgres.create.ex | 1 + lib/mix/tasks/ash_postgres.drop.ex | 1 + lib/mix/tasks/ash_postgres.migrate.ex | 2 ++ lib/mix/tasks/ash_postgres.rollback.ex | 1 + 4 files changed, 5 insertions(+) diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index bfe117b1..a99baf33 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -28,6 +28,7 @@ defmodule Mix.Tasks.AshPostgres.Create do ## Command line options * `--domains` - the domains who's repos you want to migrate. + * `-r, --repo` - the repo to create * `--quiet` - do not log output * `--no-compile` - do not compile before creating * `--no-deps-check` - do not compile before creating diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index a79a1025..43028c38 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -31,6 +31,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do ## Command line options * `--domains` - the domains who's repos should be dropped + * `-r, --repo` - the repo to drop * `-q`, `--quiet` - run the command quietly * `-f`, `--force` - do not ask for confirmation when dropping the database. Configuration is asked only when `:start_permanent` is set to true diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 81e4d63f..36b9187d 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -74,6 +74,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do * `--all` - run all pending migrations + * `--repo`, `-r` - the repo to migrate + * `--step`, `-n` - run n number of pending migrations * `--to` - run all migrations up to and including version diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index 211dbdff..b9a8354a 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -32,6 +32,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do ## Command line options * `--domains` - the domains who's repos should be rolledback * `--all` - revert all applied migrations + * `--repo`, `-r` - the repo to rollback * `--step` / `-n` - revert n number of applied migrations * `--to` / `-v` - revert all migrations down to and including version * `--quiet` - do not log migration commands From 305b7ccccb0494f46b758f22572439ead6dcf6f2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 31 Mar 2025 00:06:00 +0100 Subject: [PATCH 0989/1215] fix: use subqueries for join resources --- lib/data_layer.ex | 8 ++++---- test/support/resources/post.ex | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 10e69221..00801cb0 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1189,11 +1189,11 @@ defmodule AshPostgres.DataLayer do destination in query, select_merge: %{__order__: over(row_number(), :order)}, join: - through in ^set_subquery_prefix( + through in subquery(set_subquery_prefix( through_query, source_query, relationship.through - ), + )), as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == @@ -1227,11 +1227,11 @@ defmodule AshPostgres.DataLayer do from( destination in query, join: - through in ^set_subquery_prefix( + through in subquery(set_subquery_prefix( through_query, source_query, relationship.through - ), + )), as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 602007aa..03cffb30 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -533,6 +533,7 @@ defmodule AshPostgres.Test.Post do public?(true) destination_attribute(:post_id) + filter expr(not is_nil(post.id)) end many_to_many :co_authors, AshPostgres.Test.Author do From 202d55b86ddb6f2963f53893617a94bec2bd4af0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 31 Mar 2025 00:09:00 +0100 Subject: [PATCH 0990/1215] chore: only use subquery for through resource if it has joins --- lib/data_layer.ex | 26 +++++++++++++++----------- test/support/resources/post.ex | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 00801cb0..5cb460f8 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1182,18 +1182,26 @@ defmodule AshPostgres.DataLayer do {:ok, through_query} -> through_query = Ecto.Query.exclude(through_query, :select) + through_query = + if through_query.joins && through_query.joins != [] do + subquery( + set_subquery_prefix( + through_query, + source_query, + relationship.through + ) + ) + else + through_query + end + if query.__ash_bindings__[:__order__?] do subquery = subquery( from( destination in query, select_merge: %{__order__: over(row_number(), :order)}, - join: - through in subquery(set_subquery_prefix( - through_query, - source_query, - relationship.through - )), + join: through in ^through_query, as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == @@ -1227,11 +1235,7 @@ defmodule AshPostgres.DataLayer do from( destination in query, join: - through in subquery(set_subquery_prefix( - through_query, - source_query, - relationship.through - )), + through in ^through_query, as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 03cffb30..1173f5f4 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -533,7 +533,7 @@ defmodule AshPostgres.Test.Post do public?(true) destination_attribute(:post_id) - filter expr(not is_nil(post.id)) + filter(expr(not is_nil(post.id))) end many_to_many :co_authors, AshPostgres.Test.Author do From f573576a236331659af0c02a5186543382f9cdeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 10:26:51 +0100 Subject: [PATCH 0991/1215] chore(deps): bump the production-dependencies group with 2 updates (#523) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.5.0 to 3.5.2 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/commits/v3.5.2) Updates `igniter` from 0.5.40 to 0.5.43 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.40...v0.5.43) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-version: 0.5.43 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index a7225498..7255eb9b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.0", "8b2342b1a048bdde5cde7bb2696ba0f90ff754785a25648785e706b5f632ae10", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0c5a9aec7e0f611858bcce933d6677445ae0a63d45cc0c56f02f4aea7fff6cc2"}, + "ash": {:hex, :ash, "3.5.2", "953aebdaca92a00c5ec88988463ff80dc5ea06908b51e7b0e7e843f68bf7cb6c", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b5cdc43ee58e87380476ec54a6222dc408ccc9580ebcaa3b3e24fd18cc507fd2"}, "ash_sql": {:hex, :ash_sql, "0.2.66", "7021417635367802eaeef0efbfe3116ad1c9e5f690d469708ce0bf97b13d7bbf", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a49b9acb0c14090329ffbe1df8e7b9e8713b377448680c7c204a7483c0ee4899"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.40", "c5ad8094852173187eeef7dfabd6110df49047ed649d68a549584d9ede0118fc", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "161155d1ed443b60d52c84f01b31130d2156f6ddce581e6c46f496f93de1d804"}, + "igniter": {:hex, :igniter, "0.5.43", "92dcc3e58f7cc78bacc0e560516020e14d5f01b3ac3761e32816e1e8d101eea7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d206f9699b4e94d133abaeadbd18a2b2a89bcb3456facb2e1f4d79cd03327bdc"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -44,12 +44,12 @@ "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, + "sourceror": {:hex, :sourceror, "1.8.0", "945d6cda4125f54df9def93b83b7fc8dc52008f9cb69b5883c8bd3ef86769a7d", [:mix], [], "hexpm", "0bb5186726261dece0a13a7f509d389239c23a9447ca612140f5497707452980"}, "spark": {:hex, :spark, "2.2.48", "dd1005c26c7f98ea686a951f7ae58fffb54eff19c47830e6ff68b93f87433baa", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "379912647b9ddcc5265e91a82a235a264a727123d1f9e90052d91ad8cebbb2d0"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"}, + "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, From 21347dc6101e9623f9a3c6ecdfb18a5c91729e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ska=C5=82ecki?= Date: Wed, 9 Apr 2025 10:27:56 +0200 Subject: [PATCH 0992/1215] docs: Added link to all supported reference options (#525) --- documentation/topics/resources/references.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/topics/resources/references.md b/documentation/topics/resources/references.md index 91328ddc..10ed6575 100644 --- a/documentation/topics/resources/references.md +++ b/documentation/topics/resources/references.md @@ -14,6 +14,8 @@ postgres do end ``` +All supported DSL options can be found in a [datalayer documentation](https://hexdocs.pm/ash_postgres/dsl-ashpostgres-datalayer.html#postgres-references). + > ### Actions are not used for this behavior {: .warning} > > No resource logic is applied with these operations! No authorization rules or validations take place, and no notifications are issued. This operation happens _directly_ in the database. From 69e610b1370637e3f071f7e8d4ba4e678b2c0aaa Mon Sep 17 00:00:00 2001 From: artiom Date: Thu, 10 Apr 2025 00:08:38 +0100 Subject: [PATCH 0993/1215] fix: ash postgres subquery usage (#524) --- lib/data_layer.ex | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5cb460f8..c4295a7a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1041,12 +1041,16 @@ defmodule AshPostgres.DataLayer do Map.get(relationship, :manual) -> {module, opts} = relationship.manual - module.ash_postgres_subquery( - opts, - 0, - 0, - base_query - ) + case module.ash_postgres_subquery(opts, 0, 0, base_query) do + {:ok, subquery} -> + subquery + + {:error, error} -> + {:error, error} + + subquery -> + subquery + end Map.get(relationship, :no_attributes?) -> base_query @@ -1234,8 +1238,7 @@ defmodule AshPostgres.DataLayer do subquery( from( destination in query, - join: - through in ^through_query, + join: through in ^through_query, as: ^through_binding, on: field(through, ^destination_attribute_on_join_resource) == From bb14fc0f7fdb923f4120e8efebfa0ad3ab44ac7c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 9 Apr 2025 19:15:44 -0400 Subject: [PATCH 0994/1215] chore: release version v2.5.15 --- CHANGELOG.md | 17 +++++++++++++++++ mix.exs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b78a98..4a02f4f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.15](https://github.com/ash-project/ash_postgres/compare/v2.5.14...v2.5.15) (2025-04-09) + + + + +### Bug Fixes: + +* ash postgres subquery usage (#524) + +* use subqueries for join resources + +* use schema when changing reference deferrability (#519) + +### Improvements: + +* propagate `-r` flag to Ecto (#521) + ## [v2.5.14](https://github.com/ash-project/ash_postgres/compare/v2.5.13...v2.5.14) (2025-03-28) diff --git a/mix.exs b/mix.exs index 9567037f..f9c27376 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.14" + @version "2.5.15" def project do [ From f0283e893877a7995a4381c496ee8ec13bd00d6f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 9 Apr 2025 23:41:07 -0400 Subject: [PATCH 0995/1215] fix: use proper migrations path configuration --- lib/migration_generator/migration_generator.ex | 2 +- lib/repo.ex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 73fe1d3d..ada1b7b7 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -912,7 +912,7 @@ defmodule AshPostgres.MigrationGenerator do Path.join(priv, "tenant_migrations") end else - if path = opts.migration_path || config[:tenant_migrations_path] do + if path = opts.migration_path || config[:migrations_path] do path else priv = diff --git a/lib/repo.ex b/lib/repo.ex index 02d8c703..eb1beebf 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -304,6 +304,7 @@ defmodule AshPostgres.Repo do prefer_transaction?: 0, prefer_transaction_for_atomic_updates?: 0, tenant_migrations_path: 0, + migrations_path: 0, default_prefix: 0, override_migration_type: 1, create?: 0, From a9131244e077b9da2eda2ec239054e071d59d085 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:14:15 -0400 Subject: [PATCH 0996/1215] chore(deps): bump the production-dependencies group with 3 updates (#526) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.5.2 to 3.5.3 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.2...v3.5.3) Updates `ash_sql` from 0.2.66 to 0.2.67 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.66...v0.2.67) Updates `igniter` from 0.5.43 to 0.5.44 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.43...v0.5.44) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-version: 0.2.67 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-version: 0.5.44 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.lock b/mix.lock index 7255eb9b..25487300 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.2", "953aebdaca92a00c5ec88988463ff80dc5ea06908b51e7b0e7e843f68bf7cb6c", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b5cdc43ee58e87380476ec54a6222dc408ccc9580ebcaa3b3e24fd18cc507fd2"}, - "ash_sql": {:hex, :ash_sql, "0.2.66", "7021417635367802eaeef0efbfe3116ad1c9e5f690d469708ce0bf97b13d7bbf", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a49b9acb0c14090329ffbe1df8e7b9e8713b377448680c7c204a7483c0ee4899"}, + "ash": {:hex, :ash, "3.5.3", "4ac587b12d5862adeae3d46961cd42bd51de3d1b8417e367fefcb213579759be", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bb5ba4451d8cd80a2988cf5126402dd385a9fc7967aa29b4746cd0098f58e8f5"}, + "ash_sql": {:hex, :ash_sql, "0.2.67", "d84ca5432aa6195ba14499ee338e42505e5c7cf217e1d3a48d857c18ba65116c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "9147993d3740ee12096562610f4df3662a6e61e66b5e2a03846f6764d1a9451e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.43", "92dcc3e58f7cc78bacc0e560516020e14d5f01b3ac3761e32816e1e8d101eea7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d206f9699b4e94d133abaeadbd18a2b2a89bcb3456facb2e1f4d79cd03327bdc"}, + "igniter": {:hex, :igniter, "0.5.44", "a66eb1c74af98c5020ceab5520bbacb55f5f7ffa81854c2dcc1bfe682fd4a75d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c47d09b8edb58b092674235e7a9cf1c5ec26f9d45b7afc18fe458f0790251608"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,13 +39,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.15.0", "556937d9310e1a6dd06083592b9eb9e0d212540b6d82faecba70823ee7a0747d", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f634383a7760ba3106d31a3185f2e2c39e1485d899d884d94c22c62c9b5e7a4a"}, + "reactor": {:hex, :reactor, "0.15.1", "91e17d275f268bc33cb02bbf3d9438704073a20b29a5008c594ddf4c01b307f3", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "40bba308c099ddad237211a7f1b1f500f5dec5f4dd6f775f1a63b0a1b80e50ba"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.8.0", "945d6cda4125f54df9def93b83b7fc8dc52008f9cb69b5883c8bd3ef86769a7d", [:mix], [], "hexpm", "0bb5186726261dece0a13a7f509d389239c23a9447ca612140f5497707452980"}, - "spark": {:hex, :spark, "2.2.48", "dd1005c26c7f98ea686a951f7ae58fffb54eff19c47830e6ff68b93f87433baa", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "379912647b9ddcc5265e91a82a235a264a727123d1f9e90052d91ad8cebbb2d0"}, + "sourceror": {:hex, :sourceror, "1.8.2", "f486ddded3b884175583413b431178b691b42d3e616f1ee80bed15503c5f7fd7", [:mix], [], "hexpm", "3f3126d50c222e1029c31861165e73e9c89ebcd543d2896192e2c1d792688fef"}, + "spark": {:hex, :spark, "2.2.49", "6307ddd0359d40a48ad224e5bcb602f2984da036778c4c074dc66110374b5a7a", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "45c0efdf23334f89fa80a4303196902029c73b219b06830bb906ccf21e440cd0"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From a8e385e607c41e4c74ecc6fd1704166858c978d8 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 12 Apr 2025 10:35:32 -0400 Subject: [PATCH 0997/1215] fix: fixes for map types nested in expressions requires latest ash_sql for build to pass --- lib/sql_implementation.ex | 52 ++++++++++++++-------------------- test/calculation_test.exs | 12 ++++++++ test/support/resources/post.ex | 19 +++++++++++++ 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 8f530861..6648a7e4 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -23,16 +23,16 @@ defmodule AshPostgres.SqlImplementation do nil {:array, type} -> - parameterized_type({:array, Ash.Type.get_type(type)}, [], false) + parameterized_type({:array, Ash.Type.get_type(type)}, []) {:array, type, constraints} -> - parameterized_type({:array, Ash.Type.get_type(type)}, constraints, false) + parameterized_type({:array, Ash.Type.get_type(type)}, constraints) {type, constraints} -> - parameterized_type(type, constraints, false) + parameterized_type(type, constraints) type -> - parameterized_type(type, [], false) + parameterized_type(type, []) end end @@ -241,22 +241,20 @@ defmodule AshPostgres.SqlImplementation do end @impl true - def parameterized_type(type, constraints, no_maps? \\ true) - - def parameterized_type({:parameterized, _} = type, _, _) do + def parameterized_type({:parameterized, _} = type, _) do type end - def parameterized_type({:parameterized, _, _} = type, _, _) do + def parameterized_type({:parameterized, _, _} = type, _) do type end - def parameterized_type({:in, type}, constraints, no_maps?) do - parameterized_type({:array, type}, constraints, no_maps?) + def parameterized_type({:in, type}, constraints) do + parameterized_type({:array, type}, constraints) end - def parameterized_type({:array, type}, constraints, _) do - case parameterized_type(type, constraints[:items] || [], false) do + def parameterized_type({:array, type}, constraints) do + case parameterized_type(type, constraints[:items] || []) do nil -> nil @@ -265,31 +263,23 @@ defmodule AshPostgres.SqlImplementation do end end - def parameterized_type({type, constraints}, [], no_maps?) do - parameterized_type(type, constraints, no_maps?) + def parameterized_type({type, constraints}, []) do + parameterized_type(type, constraints) end - def parameterized_type(Ash.Type.CiString, constraints, no_maps?) do - parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) + def parameterized_type(Ash.Type.CiString, constraints) do + parameterized_type(AshPostgres.Type.CiStringWrapper, constraints) end - def parameterized_type(Ash.Type.String, constraints, no_maps?) do - parameterized_type(AshPostgres.Type.StringWrapper, constraints, no_maps?) + def parameterized_type(Ash.Type.String, constraints) do + parameterized_type(AshPostgres.Type.StringWrapper, constraints) end - def parameterized_type(:tsquery, constraints, no_maps?) do - parameterized_type(AshPostgres.Tsquery, constraints, no_maps?) + def parameterized_type(:tsquery, constraints) do + parameterized_type(AshPostgres.Tsquery, constraints) end - def parameterized_type(type, _constraints, false) - when type in [Ash.Type.Map, Ash.Type.Map.EctoType], - do: :map - - def parameterized_type(type, _constraints, true) - when type in [Ash.Type.Map, Ash.Type.Map.EctoType], - do: nil - - def parameterized_type(type, constraints, no_maps?) do + def parameterized_type(type, constraints) do if Ash.Type.ash_type?(type) do cast_in_query? = if function_exported?(Ash.Type, :cast_in_query?, 2) do @@ -301,7 +291,7 @@ defmodule AshPostgres.SqlImplementation do if cast_in_query? do type = Ash.Type.ecto_type(type) - parameterized_type(type, constraints, no_maps?) + parameterized_type(type, constraints) else nil end @@ -312,7 +302,7 @@ defmodule AshPostgres.SqlImplementation do else case type.type(constraints || []) do :ci_string -> - parameterized_type(AshPostgres.Type.CiStringWrapper, constraints, no_maps?) + parameterized_type(AshPostgres.Type.CiStringWrapper, constraints) _ -> Ecto.ParameterizedType.init(type, constraints || []) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 94d5e5ec..017ca25a 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -776,6 +776,18 @@ defmodule AshPostgres.CalculationTest do end describe "maps" do + test "literal maps inside of conds can be loaded" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "match", score: 42}) + |> Ash.create!() + + assert Ash.load!(post, :literal_map_in_expr).literal_map_in_expr == %{ + match: true, + of: "match" + } + end + test "maps can reference filtered aggregates" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 1173f5f4..97e6131c 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -789,6 +789,25 @@ defmodule AshPostgres.Test.Post do load: [:count_of_comments, :count_of_linked_posts] ) + calculate( + :literal_map_in_expr, + :map, + expr( + cond do + title == "match" -> + %{match: true, of: "match"} + + title == "not-match" -> + %{match: true, of: "not-match"} + + true -> + %{match: false} + end + ) + ) do + constraints(fields: [match: [type: :boolean], of: [type: :string]]) + end + calculate :similarity, :boolean, expr(fragment("(to_tsvector(?) @@ ?)", title, ^arg(:search))) do From 123d137389a138ebaa3702e85f0a0e3e91993840 Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Sun, 13 Apr 2025 11:08:56 +0800 Subject: [PATCH 0998/1215] chore: Add `@impl true` to all callback functions in generated Repo module All functions such as `installed_extensions/0` and `min_pg_version/0` are callbacks, and should be marked as such --- lib/mix/tasks/ash_postgres.install.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 555d89e7..364237dd 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -571,6 +571,7 @@ if Code.ensure_loaded?(Igniter) do _ -> {:ok, Igniter.Code.Common.add_code(zipper, """ + @impl true def installed_extensions do # Add extensions here, and the migration generator will install them. ["ash-functions"] @@ -589,6 +590,7 @@ if Code.ensure_loaded?(Igniter) do Igniter.Code.Common.add_code(zipper, """ # Don't open unnecessary transactions # will default to `false` in 4.0 + @impl true def prefer_transaction? do false end @@ -604,6 +606,7 @@ if Code.ensure_loaded?(Igniter) do _ -> {:ok, Igniter.Code.Common.add_code(zipper, """ + @impl true def min_pg_version do %Version{major: #{version.major}, minor: #{version.minor}, patch: #{version.patch}} end From 0e38376e7c6418ea4ca926faebeee8580a7b67d1 Mon Sep 17 00:00:00 2001 From: Oliver Severin Mulelid-Tynes Date: Sun, 13 Apr 2025 22:23:24 +0200 Subject: [PATCH 0999/1215] test: Add test that provokes filter alias collision with aggregates (#529) --- mix.exs | 3 +- mix.lock | 8 +- .../test_repo/comedians/20250413141328.json | 88 ++++++++++++ .../test_repo/jokes/20250413141328.json | 127 ++++++++++++++++++ .../test_repo/punchlines/20250413141328.json | 78 +++++++++++ .../standup_clubs/20250413141328.json | 59 ++++++++ ...41328_add_punchlines_and_standup_clubs.exs | 119 ++++++++++++++++ test/aggregate_test.exs | 56 ++++++++ test/support/domain.ex | 2 + test/support/resources/comedian.ex | 5 + test/support/resources/joke.ex | 6 + test/support/resources/punchline.ex | 26 ++++ test/support/resources/standup_club.ex | 32 +++++ 13 files changed, 604 insertions(+), 5 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/comedians/20250413141328.json create mode 100644 priv/resource_snapshots/test_repo/jokes/20250413141328.json create mode 100644 priv/resource_snapshots/test_repo/punchlines/20250413141328.json create mode 100644 priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json create mode 100644 priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs create mode 100644 test/support/resources/punchline.ex create mode 100644 test/support/resources/standup_club.ex diff --git a/mix.exs b/mix.exs index f9c27376..a1e5afe9 100644 --- a/mix.exs +++ b/mix.exs @@ -167,7 +167,8 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.62")}, + # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.62")}, + {:ash_sql, github: "ash-project/ash_sql", branch: "main", override: true}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index 25487300..8549a3af 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.3", "4ac587b12d5862adeae3d46961cd42bd51de3d1b8417e367fefcb213579759be", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bb5ba4451d8cd80a2988cf5126402dd385a9fc7967aa29b4746cd0098f58e8f5"}, - "ash_sql": {:hex, :ash_sql, "0.2.67", "d84ca5432aa6195ba14499ee338e42505e5c7cf217e1d3a48d857c18ba65116c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "9147993d3740ee12096562610f4df3662a6e61e66b5e2a03846f6764d1a9451e"}, + "ash": {:hex, :ash, "3.5.4", "c401c978a3107c1763143841673ba2b26e007916754fffe67f1b797131db900e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7712253e960921f26b6a873e6b50d6ef747f14f2d7a41e19882ac9dd5ac353f8"}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "eceb23ed9440a967d329e7021aa5e6f3fdb05f1b", [branch: "main"]}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.44", "a66eb1c74af98c5020ceab5520bbacb55f5f7ffa81854c2dcc1bfe682fd4a75d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c47d09b8edb58b092674235e7a9cf1c5ec26f9d45b7afc18fe458f0790251608"}, + "igniter": {:hex, :igniter, "0.5.45", "9307c6b466b6f6525b5ec7e4d8728bfbdaa376a241f70e09a40d4bb5fe095cfb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c34aa1aba378c21cd7969d7463cb76b7b02c51c134a75f68ca758d108ffa7736"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -44,7 +44,7 @@ "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.8.2", "f486ddded3b884175583413b431178b691b42d3e616f1ee80bed15503c5f7fd7", [:mix], [], "hexpm", "3f3126d50c222e1029c31861165e73e9c89ebcd543d2896192e2c1d792688fef"}, + "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"}, "spark": {:hex, :spark, "2.2.49", "6307ddd0359d40a48ad224e5bcb602f2984da036778c4c074dc66110374b5a7a", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "45c0efdf23334f89fa80a4303196902029c73b219b06830bb906ccf21e440cd0"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, diff --git a/priv/resource_snapshots/test_repo/comedians/20250413141328.json b/priv/resource_snapshots/test_repo/comedians/20250413141328.json new file mode 100644 index 00000000..01af0669 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comedians/20250413141328.json @@ -0,0 +1,88 @@ +{ + "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?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "comedians_standup_club_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "standup_clubs" + }, + "size": null, + "source": "standup_club_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "6F2F271F1118A34E36898BF810B56D211D82E38CF11852AFF656FAB8E6DF3C24", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "comedians" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/jokes/20250413141328.json b/priv/resource_snapshots/test_repo/jokes/20250413141328.json new file mode 100644 index 00000000..7a2dcac1 --- /dev/null +++ b/priv/resource_snapshots/test_repo/jokes/20250413141328.json @@ -0,0 +1,127 @@ +{ + "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": "text", + "type": "text" + }, + { + "allow_nil?": true, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "is_good", + "type": "boolean" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "jokes_standup_club_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "standup_clubs" + }, + "size": null, + "source": "standup_club_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "jokes_comedian_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "comedians" + }, + "size": null, + "source": "comedian_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "D91D184F68DA42DC309DEBA727AF7FC08A625E89CA3DBD9DD191DF83433F8685", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "jokes" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/punchlines/20250413141328.json b/priv/resource_snapshots/test_repo/punchlines/20250413141328.json new file mode 100644 index 00000000..44a77094 --- /dev/null +++ b/priv/resource_snapshots/test_repo/punchlines/20250413141328.json @@ -0,0 +1,78 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "punchlines_joke_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "jokes" + }, + "size": null, + "source": "joke_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "0262D383DEF61B2296F47C93580740F8FD2A23A30E80B5929133F3C9E7AB13B2", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "punchlines" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json b/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json new file mode 100644 index 00000000..e0342b9a --- /dev/null +++ b/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json @@ -0,0 +1,59 @@ +{ + "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?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "1EAC09FCC489E3F2F6A9904F4D59369CC5EAA0C3B821F1F4B0D58B1650DC44CB", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "standup_clubs" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs b/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs new file mode 100644 index 00000000..1e565f40 --- /dev/null +++ b/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs @@ -0,0 +1,119 @@ +defmodule AshPostgres.TestRepo.Migrations.AddPunchlinesAndStandupClubs 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(:jokes) do + add(:standup_club_id, :uuid) + end + + alter table(:comedians) do + add(:standup_club_id, :uuid) + end + + create table(:standup_clubs, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:jokes) do + modify( + :standup_club_id, + references(:standup_clubs, + column: :id, + name: "jokes_standup_club_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:comedians) do + modify( + :standup_club_id, + references(:standup_clubs, + column: :id, + name: "comedians_standup_club_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:standup_clubs) do + add(:name, :text) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + + create table(:punchlines, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + + add(:inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add(:updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + + add( + :joke_id, + references(:jokes, + column: :id, + name: "punchlines_joke_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + end + + def down do + drop(constraint(:punchlines, "punchlines_joke_id_fkey")) + + drop(table(:punchlines)) + + alter table(:standup_clubs) do + remove(:updated_at) + remove(:inserted_at) + remove(:name) + end + + drop(constraint(:comedians, "comedians_standup_club_id_fkey")) + + alter table(:comedians) do + modify(:standup_club_id, :uuid) + end + + drop(constraint(:jokes, "jokes_standup_club_id_fkey")) + + alter table(:jokes) do + modify(:standup_club_id, :uuid) + end + + drop(table(:standup_clubs)) + + alter table(:comedians) do + remove(:standup_club_id) + end + + alter table(:jokes) do + remove(:standup_club_id) + end + end +end diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index d3aeec87..68a52483 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1579,4 +1579,60 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end end + + test "filter and aggregate names do not collide with the same names" do + club = Ash.Seed.seed!(AshPostgres.Test.StandupClub, %{name: "Studio 54"}) + + club_comedians = + Enum.map([1, 2, 3], fn idx -> + Ash.Seed.seed!(AshPostgres.Test.Comedian, %{ + name: "Bill Burr-#{idx}", + standup_club_id: club.id + }) + end) + + # |> IO.inspect() + + Enum.each(club_comedians, fn comedian -> + Range.new(1, Enum.random([2, 3, 4, 5, 6])) + |> Enum.each(fn joke_idx -> + joke = + Ash.Seed.seed!(AshPostgres.Test.Joke, %{ + comedian_id: comedian.id, + text: "Haha I am a joke number #{joke_idx}" + }) + + Range.new(1, Enum.random([2, 3, 4, 5, 6])) + |> Enum.each(fn idx -> + Ash.Seed.seed!(AshPostgres.Test.Punchline, %{joke_id: joke.id}) + end) + end) + end) + + Range.new(1, Enum.random([2, 3, 4, 5, 6])) + |> Enum.each(fn joke_idx -> + joke = + Ash.Seed.seed!(AshPostgres.Test.Joke, %{ + standup_club_id: club.id, + text: "Haha I am a club joke number #{joke_idx}" + }) + + Range.new(1, Enum.random([2, 3, 4, 5, 6])) + |> Enum.each(fn idx -> + Ash.Seed.seed!(AshPostgres.Test.Punchline, %{joke_id: joke.id}) + end) + end) + + filter = %{ + comedians: %{ + jokes: %{ + punchline_count: %{ + greater_than: 0 + } + } + } + } + Ash.Query.filter_input(AshPostgres.Test.StandupClub, filter) + |> Ash.read!(load: [:punchline_count, comedians: [jokes: :punchline_count]]) + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index 36cdab5c..eb1f047c 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -35,6 +35,8 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.DbPoint) resource(AshPostgres.Test.DbStringPoint) resource(AshPostgres.Test.CSV) + resource(AshPostgres.Test.StandupClub) + resource(AshPostgres.Test.Punchline) end authorization do diff --git a/test/support/resources/comedian.ex b/test/support/resources/comedian.ex index a210270c..a61f3c41 100644 --- a/test/support/resources/comedian.ex +++ b/test/support/resources/comedian.ex @@ -13,6 +13,7 @@ defmodule AshPostgres.Test.Comedian do end relationships do + belongs_to(:standup_club, AshPostgres.Test.StandupClub, public?: true) has_many(:jokes, AshPostgres.Test.Joke, public?: true) end @@ -21,6 +22,10 @@ defmodule AshPostgres.Test.Comedian do calculate(:has_jokes_expr, :boolean, expr(has_jokes_mod == true)) end + aggregates do + count(:punchline_count, [:jokes, :punchlines], public?: true) + end + actions do defaults([:read]) diff --git a/test/support/resources/joke.ex b/test/support/resources/joke.ex index a97b1e3f..7f6318f8 100644 --- a/test/support/resources/joke.ex +++ b/test/support/resources/joke.ex @@ -14,13 +14,19 @@ defmodule AshPostgres.Test.Joke do end relationships do + belongs_to(:standup_club, AshPostgres.Test.StandupClub, public?: true) belongs_to(:comedian, AshPostgres.Test.Comedian, public?: true) + has_many(:punchlines, AshPostgres.Test.Punchline, public?: true) end actions do defaults([:read]) end + aggregates do + count(:punchline_count, :punchlines, public?: true) + end + postgres do table("jokes") repo(AshPostgres.TestRepo) diff --git a/test/support/resources/punchline.ex b/test/support/resources/punchline.ex new file mode 100644 index 00000000..90e905d9 --- /dev/null +++ b/test/support/resources/punchline.ex @@ -0,0 +1,26 @@ +defmodule AshPostgres.Test.Punchline do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + attributes do + uuid_primary_key(:id) + create_timestamp(:inserted_at, public?: true) + update_timestamp(:updated_at, public?: true) + end + + relationships do + belongs_to(:joke, AshPostgres.Test.Joke, public?: true) + end + + actions do + defaults([:read]) + end + + postgres do + table("punchlines") + repo(AshPostgres.TestRepo) + end +end diff --git a/test/support/resources/standup_club.ex b/test/support/resources/standup_club.ex new file mode 100644 index 00000000..3dd2e075 --- /dev/null +++ b/test/support/resources/standup_club.ex @@ -0,0 +1,32 @@ +defmodule AshPostgres.Test.StandupClub do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + create_timestamp(:inserted_at, public?: true) + update_timestamp(:updated_at, public?: true) + end + + relationships do + has_many(:comedians, AshPostgres.Test.Comedian, public?: true) + has_many(:jokes, AshPostgres.Test.Joke, public?: true) + end + + actions do + defaults([:read]) + end + + aggregates do + count(:punchline_count, [:jokes, :punchlines], public?: true) + end + + postgres do + table("standup_clubs") + repo(AshPostgres.TestRepo) + end +end From 0a3086242b2f2ec92bdc42aee2a54b6b4745a2e9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 13 Apr 2025 16:23:52 -0400 Subject: [PATCH 1000/1215] chore: put deps back --- mix.exs | 3 +-- mix.lock | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index a1e5afe9..f9c27376 100644 --- a/mix.exs +++ b/mix.exs @@ -167,8 +167,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.62")}, - {:ash_sql, github: "ash-project/ash_sql", branch: "main", override: true}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.62")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index 8549a3af..42a61fc0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.4", "c401c978a3107c1763143841673ba2b26e007916754fffe67f1b797131db900e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7712253e960921f26b6a873e6b50d6ef747f14f2d7a41e19882ac9dd5ac353f8"}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "eceb23ed9440a967d329e7021aa5e6f3fdb05f1b", [branch: "main"]}, + "ash_sql": {:hex, :ash_sql, "0.2.67", "d84ca5432aa6195ba14499ee338e42505e5c7cf217e1d3a48d857c18ba65116c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "9147993d3740ee12096562610f4df3662a6e61e66b5e2a03846f6764d1a9451e"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, From 2897072324bb1e734e9b7ec17a2fbcf469e51a55 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 15 Apr 2025 14:40:27 -0400 Subject: [PATCH 1001/1215] chore: update test --- test/aggregate_test.exs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 68a52483..efae1cfa 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1580,6 +1580,7 @@ defmodule AshSql.AggregateTest do end end + @tag :regression test "filter and aggregate names do not collide with the same names" do club = Ash.Seed.seed!(AshPostgres.Test.StandupClub, %{name: "Studio 54"}) @@ -1591,8 +1592,6 @@ defmodule AshSql.AggregateTest do }) end) - # |> IO.inspect() - Enum.each(club_comedians, fn comedian -> Range.new(1, Enum.random([2, 3, 4, 5, 6])) |> Enum.each(fn joke_idx -> @@ -1603,7 +1602,7 @@ defmodule AshSql.AggregateTest do }) Range.new(1, Enum.random([2, 3, 4, 5, 6])) - |> Enum.each(fn idx -> + |> Enum.each(fn _idx -> Ash.Seed.seed!(AshPostgres.Test.Punchline, %{joke_id: joke.id}) end) end) @@ -1618,7 +1617,7 @@ defmodule AshSql.AggregateTest do }) Range.new(1, Enum.random([2, 3, 4, 5, 6])) - |> Enum.each(fn idx -> + |> Enum.each(fn _idx -> Ash.Seed.seed!(AshPostgres.Test.Punchline, %{joke_id: joke.id}) end) end) @@ -1632,7 +1631,8 @@ defmodule AshSql.AggregateTest do } } } + Ash.Query.filter_input(AshPostgres.Test.StandupClub, filter) - |> Ash.read!(load: [:punchline_count, comedians: [jokes: :punchline_count]]) + |> Ash.read!(load: [:punchline_count]) end end From edf5139a7b14c5307dbcea4f53011aac58944e6e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 15 Apr 2025 15:26:37 -0400 Subject: [PATCH 1002/1215] chore: update dependencies --- mix.exs | 2 +- mix.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index f9c27376..4112123a 100644 --- a/mix.exs +++ b/mix.exs @@ -167,7 +167,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.62")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.68")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index 42a61fc0..9fd68d6c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.4", "c401c978a3107c1763143841673ba2b26e007916754fffe67f1b797131db900e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7712253e960921f26b6a873e6b50d6ef747f14f2d7a41e19882ac9dd5ac353f8"}, - "ash_sql": {:hex, :ash_sql, "0.2.67", "d84ca5432aa6195ba14499ee338e42505e5c7cf217e1d3a48d857c18ba65116c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "9147993d3740ee12096562610f4df3662a6e61e66b5e2a03846f6764d1a9451e"}, + "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"}, + "ash_sql": {:hex, :ash_sql, "0.2.68", "43ac2cae2d78d273542032f9176d452d70954ec59e427ba87f83882353759c43", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a9cc1261b956e9a691b84ece66a8fe6d050a993d4d479021e2578ee926d8e843"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.45", "9307c6b466b6f6525b5ec7e4d8728bfbdaa376a241f70e09a40d4bb5fe095cfb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c34aa1aba378c21cd7969d7463cb76b7b02c51c134a75f68ca758d108ffa7736"}, + "igniter": {:hex, :igniter, "0.5.46", "e3ad5b07a194b6e550ddd303bac45a126a65c6157c8acb664b22011cac8e34fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "64c59b696b678b2b83e2ee923f5254ac6479aff6c65dd513383bc0e4cdaeeeb7"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -39,13 +39,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.15.1", "91e17d275f268bc33cb02bbf3d9438704073a20b29a5008c594ddf4c01b307f3", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "40bba308c099ddad237211a7f1b1f500f5dec5f4dd6f775f1a63b0a1b80e50ba"}, + "reactor": {:hex, :reactor, "0.15.2", "8c1b3fe0527b7a92b0b22c3f33f2e66858dd069bf1dd51d1031f63cd8cbd1fd5", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "091435a1fa0cab9bc2ed3934b203a0fd190f62e8b6aca63741f9242b8c7631ac"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"}, - "spark": {:hex, :spark, "2.2.49", "6307ddd0359d40a48ad224e5bcb602f2984da036778c4c074dc66110374b5a7a", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "45c0efdf23334f89fa80a4303196902029c73b219b06830bb906ccf21e440cd0"}, + "spark": {:hex, :spark, "2.2.51", "7ed629fc46cdb87c36796c6abf6361f20ee3c7c9a19204e9f4e02a4ce239ce7e", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "996487c3913a4a32c39134e7e7654f0e7fe90d182eb47f3f4933bdeb6792b9b1"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 0afec6a07d44bf093f0ab8bdca2b2be272b35a23 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 15 Apr 2025 15:26:47 -0400 Subject: [PATCH 1003/1215] chore: release version v2.5.16 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a02f4f0..13990a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.16](https://github.com/ash-project/ash_postgres/compare/v2.5.15...v2.5.16) (2025-04-15) + + + + +### Bug Fixes: + +* fixes for map types nested in expressions + +* use proper migrations path configuration + ## [v2.5.15](https://github.com/ash-project/ash_postgres/compare/v2.5.14...v2.5.15) (2025-04-09) diff --git a/mix.exs b/mix.exs index 4112123a..878ba703 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.15" + @version "2.5.16" def project do [ From 5c5b64056ea5375ea854b76b05010c5b9b445e3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 09:59:43 -0400 Subject: [PATCH 1004/1215] chore(deps): bump ash_sql in the production-dependencies group (#530) Bumps the production-dependencies group with 1 update: [ash_sql](https://github.com/ash-project/ash_sql). Updates `ash_sql` from 0.2.68 to 0.2.71 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.68...v0.2.71) --- updated-dependencies: - dependency-name: ash_sql dependency-version: 0.2.71 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 9fd68d6c..ea418a1c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"}, - "ash_sql": {:hex, :ash_sql, "0.2.68", "43ac2cae2d78d273542032f9176d452d70954ec59e427ba87f83882353759c43", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a9cc1261b956e9a691b84ece66a8fe6d050a993d4d479021e2578ee926d8e843"}, + "ash_sql": {:hex, :ash_sql, "0.2.71", "40cabdd0c7af2eaa0096b2b0eae886085fed1e3b326e20434274120e11dec2c5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "6e22da3d020aecaca9858f430828c12988c3418d252fa39be3f43fde9fd4224d"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"}, - "spark": {:hex, :spark, "2.2.51", "7ed629fc46cdb87c36796c6abf6361f20ee3c7c9a19204e9f4e02a4ce239ce7e", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "996487c3913a4a32c39134e7e7654f0e7fe90d182eb47f3f4933bdeb6792b9b1"}, + "spark": {:hex, :spark, "2.2.52", "50094275c9bbafa8e5e9eed0ab61983ee209a500e7044914ccf88e9921ae5082", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "f8de8c298bbbf7abd2a80d0ecabcefef65941f397cdbe94ce6165a121b09084f"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From eb0bc97ddb2a759eab9f34514e23b83bfab23ea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:05:39 -0400 Subject: [PATCH 1005/1215] chore(deps-dev): bump the dev-dependencies group with 2 updates (#531) Bumps the dev-dependencies group with 2 updates: [benchee](https://github.com/bencheeorg/benchee) and [credo](https://github.com/rrrene/credo). Updates `benchee` from 1.3.1 to 1.4.0 - [Release notes](https://github.com/bencheeorg/benchee/releases) - [Changelog](https://github.com/bencheeorg/benchee/blob/main/CHANGELOG.md) - [Commits](https://github.com/bencheeorg/benchee/compare/1.3.1...1.4.0) Updates `credo` from 1.7.11 to 1.7.12 - [Release notes](https://github.com/rrrene/credo/releases) - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/compare/v1.7.11...v1.7.12) --- updated-dependencies: - dependency-name: benchee dependency-version: 1.4.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: credo dependency-version: 1.7.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index ea418a1c..2f1ba954 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,9 @@ %{ "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"}, "ash_sql": {:hex, :ash_sql, "0.2.71", "40cabdd0c7af2eaa0096b2b0eae886085fed1e3b326e20434274120e11dec2c5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "6e22da3d020aecaca9858f430828c12988c3418d252fa39be3f43fde9fd4224d"}, - "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, + "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, + "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, @@ -17,7 +17,7 @@ "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"}, - "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, From 77a75fa29024a86f84e85b543c759521be54e7ad Mon Sep 17 00:00:00 2001 From: artiom Date: Fri, 18 Apr 2025 21:42:43 +0100 Subject: [PATCH 1006/1215] fix: correct order, when renaming attribute with an identity (#533) --- .../migration_generator.ex | 28 +++++++ test/migration_generator_test.exs | 77 +++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index ada1b7b7..a0c04a02 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1474,6 +1474,21 @@ defmodule AshPostgres.MigrationGenerator do false end + defp after?( + %Operation.RenameAttribute{ + old_attribute: %{source: source}, + table: table, + schema: schema + }, + %Operation.RemoveUniqueIndex{ + identity: %{keys: keys}, + table: table, + schema: schema + } + ) do + source in List.wrap(keys) + end + defp after?( %Operation.RemoveUniqueIndex{table: table, schema: schema}, %{table: table, schema: schema} @@ -1701,6 +1716,19 @@ defmodule AshPostgres.MigrationGenerator do destination_attribute in keys end + defp after?( + %Operation.AlterAttribute{ + old_attribute: %{ + source: source + }, + table: table, + schema: schema + }, + %Operation.RemoveUniqueIndex{identity: %{keys: keys}, table: table, schema: schema} + ) do + source in List.wrap(keys) + end + defp after?( %Operation.AlterAttribute{ new_attribute: %{references: %{table: table, destination_attribute: source}} diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 8c613055..1ef76978 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1062,6 +1062,83 @@ defmodule AshPostgres.MigrationGeneratorTest do assert File.read!(file2) =~ ~S[rename table(:posts), :subject, to: :title] end + test "when renaming a field with an identity, it asks which field you are renaming it to, and updates indexes in the correct order" do + defposts do + identities do + identity(:subject, [:subject]) + end + + attributes do + uuid_primary_key(:id) + attribute(:subject, :string, public?: true) + end + end + + defdomain([Post]) + + send(self(), {:mix_shell_input, :yes?, true}) + send(self(), {:mix_shell_input, :prompt, "subject"}) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [_file1, file2] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + contents = File.read!(file2) + [up_side, down_side] = String.split(contents, "def down", parts: 2) + + up_side_parts = + String.split(up_side, "\n", trim: true) + |> Enum.map(&String.trim/1) + + drop_index = + Enum.find_index(up_side_parts, fn x -> + x == "drop_if_exists unique_index(:posts, [:title], name: \"posts_title_index\")" + end) + + rename_table = + Enum.find_index(up_side_parts, fn x -> + x == "rename table(:posts), :title, to: :subject" + end) + + create_index = + Enum.find_index(up_side_parts, fn x -> + x == "create unique_index(:posts, [:subject], name: \"posts_subject_index\")" + end) + + assert drop_index < rename_table + assert rename_table < create_index + + down_side_parts = + String.split(down_side, "\n", trim: true) + |> Enum.map(&String.trim/1) + + drop_index = + Enum.find_index(down_side_parts, fn x -> + x == + "drop_if_exists unique_index(:posts, [:subject], name: \"posts_subject_index\")" + end) + + rename_table = + Enum.find_index(down_side_parts, fn x -> + x == "rename table(:posts), :subject, to: :title" + end) + + create_index = + Enum.find_index(down_side_parts, fn x -> + x == "create unique_index(:posts, [:title], name: \"posts_title_index\")" + end) + + assert drop_index < rename_table + assert rename_table < create_index + end + test "when renaming a field, it asks which field you are renaming it to, and adds it if you arent" do defposts do attributes do From 74439d5d8f99f5b9a6d0b27fb245f6d4e53c6721 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 18 Apr 2025 18:11:41 -0400 Subject: [PATCH 1007/1215] chore: temporarily use main of ash_sql --- mix.exs | 31 ++++++++++++++++--------------- mix.lock | 2 +- test/support/resources/post.ex | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/mix.exs b/mix.exs index 878ba703..56b71df6 100644 --- a/mix.exs +++ b/mix.exs @@ -167,7 +167,8 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.68")}, + # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.68")}, + {:ash_sql, github: "ash-project/ash_sql"}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, @@ -208,24 +209,24 @@ defmodule AshPostgres.MixProject do end end - defp ash_sql_version(default_version) do - case System.get_env("ASH_SQL_VERSION") do - nil -> - default_version + # defp ash_sql_version(default_version) do + # case System.get_env("ASH_SQL_VERSION") do + # nil -> + # default_version - "local" -> - [path: "../ash_sql", override: true] + # "local" -> + # [path: "../ash_sql", override: true] - "main" -> - [git: "https://github.com/ash-project/ash_sql.git"] + # "main" -> + # [git: "https://github.com/ash-project/ash_sql.git"] - version when is_binary(version) -> - "~> #{version}" + # version when is_binary(version) -> + # "~> #{version}" - version -> - version - end - end + # version -> + # version + # end + # end defp aliases do [ diff --git a/mix.lock b/mix.lock index 2f1ba954..60a1e8ac 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"}, - "ash_sql": {:hex, :ash_sql, "0.2.71", "40cabdd0c7af2eaa0096b2b0eae886085fed1e3b326e20434274120e11dec2c5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "6e22da3d020aecaca9858f430828c12988c3418d252fa39be3f43fde9fd4224d"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "91d990700d18055759004a60cc35f3074da3d1a9", []}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 97e6131c..4c251555 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -862,7 +862,7 @@ defmodule AshPostgres.Test.Post do calculate( :start_of_day, :datetime, - expr(start_of_day(fragment("now() AT TIME ZONE 'UTC'"), "EST")) + expr(start_of_day(fragment("now()"), "EST")) ) calculate(:author_count_of_posts, :integer, expr(author.count_of_posts_with_calc)) From 87820a51c325f6467844f51344f7bd8f254e2f62 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 23 Apr 2025 00:42:10 +0200 Subject: [PATCH 1008/1215] fix: add tenant to ash bindings in update (#534) --- lib/data_layer.ex | 7 +++++++ mix.exs | 31 ++++++++++++++-------------- mix.lock | 2 +- test/atomics_test.exs | 47 ++++++++++++++++++++++--------------------- 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c4295a7a..3d5f7275 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3073,6 +3073,13 @@ defmodule AshPostgres.DataLayer do changeset.context ) |> pkey_filter(changeset.data) + |> then(fn query -> + Map.put( + query, + :__ash_bindings__, + Map.put_new(query.__ash_bindings__, :tenant, changeset.tenant) + ) + end) changeset = Ash.Changeset.set_context(changeset, %{ diff --git a/mix.exs b/mix.exs index 56b71df6..690bfcdd 100644 --- a/mix.exs +++ b/mix.exs @@ -167,8 +167,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.68")}, - {:ash_sql, github: "ash-project/ash_sql"}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, @@ -209,24 +208,24 @@ defmodule AshPostgres.MixProject do end end - # defp ash_sql_version(default_version) do - # case System.get_env("ASH_SQL_VERSION") do - # nil -> - # default_version + defp ash_sql_version(default_version) do + case System.get_env("ASH_SQL_VERSION") do + nil -> + default_version - # "local" -> - # [path: "../ash_sql", override: true] + "local" -> + [path: "../ash_sql", override: true] - # "main" -> - # [git: "https://github.com/ash-project/ash_sql.git"] + "main" -> + [git: "https://github.com/ash-project/ash_sql.git"] - # version when is_binary(version) -> - # "~> #{version}" + version when is_binary(version) -> + "~> #{version}" - # version -> - # version - # end - # end + version -> + version + end + end defp aliases do [ diff --git a/mix.lock b/mix.lock index 60a1e8ac..e282ca96 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"}, + "ash_sql": {:hex, :ash_sql, "0.2.72", "3c353fd3361257310864dce131acd93e407379439cf8667d7ddb4cba99a3fe2c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0a802299580b8406258f9d72ec8c2a39b4d4b9ad49a2631d47a15d3290d7b0eb"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "91d990700d18055759004a60cc35f3074da3d1a9", []}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, diff --git a/test/atomics_test.exs b/test/atomics_test.exs index e3e13395..301cf8e3 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -3,6 +3,7 @@ defmodule AshPostgres.AtomicsTest do alias AshPostgres.Test.Comment use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Invite alias AshPostgres.Test.Post alias AshPostgres.Test.User @@ -367,29 +368,29 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end - # assert_raise Ash.Error.Invalid, ~r/Can only update if Post has no comments/, fn -> - # post - # |> Ash.Changeset.new() - # |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) - # |> Ash.Changeset.for_update(:update_if_no_comments_non_atomic, %{title: "bar"}) - # |> Ash.update!() - # end - - # assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> - # post - # |> Ash.Changeset.new() - # |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) - # |> Ash.Changeset.for_destroy(:destroy_if_no_comments_non_atomic, %{}) - # |> Ash.destroy!() - # end - - # assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> - # post - # |> Ash.Changeset.new() - # |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) - # |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) - # |> Ash.destroy!() - # end + assert_raise Ash.Error.Invalid, ~r/Can only update if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_update(:update_if_no_comments_non_atomic, %{title: "bar"}) + |> Ash.update!() + end + + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_destroy(:destroy_if_no_comments_non_atomic, %{}) + |> Ash.destroy!() + end + + assert_raise Ash.Error.Invalid, ~r/Can only delete if Post has no comments/, fn -> + post + |> Ash.Changeset.new() + |> Ash.Changeset.put_context(:aggregate, unquote(aggregate)) + |> Ash.Changeset.for_destroy(:destroy_if_no_comments, %{}) + |> Ash.destroy!() + end end end ) From 0124cb4c82f9327c3565103f0a3f135f1292a04f Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Wed, 23 Apr 2025 00:44:22 +0200 Subject: [PATCH 1009/1215] chore: release version v2.5.17 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13990a90..bdd05904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.17](https://github.com/ash-project/ash_postgres/compare/v2.5.16...v2.5.17) (2025-04-22) + + + + +### Bug Fixes: + +* add tenant to ash bindings in update (#534) + +* correct order, when renaming attribute with an identity (#533) + ## [v2.5.16](https://github.com/ash-project/ash_postgres/compare/v2.5.15...v2.5.16) (2025-04-15) diff --git a/mix.exs b/mix.exs index 690bfcdd..7fbd4dac 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.16" + @version "2.5.17" def project do [ From 074b580f77dd47dac463eb6815d40627ea4fe7e3 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Wed, 23 Apr 2025 12:56:25 -0600 Subject: [PATCH 1010/1215] fix: use old multitenancy in generated removals of previous indexes (#536) --- lib/migration_generator/operation.ex | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 14f7b52b..475b88e5 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1219,29 +1219,38 @@ defmodule AshPostgres.MigrationGenerator.Operation do defmodule RemoveCustomIndex do @moduledoc false - defstruct [:schema, :table, :index, :base_filter, :multitenancy, no_phase: true] + defstruct [ + :schema, + :table, + :index, + :base_filter, + :multitenancy, + :old_multitenancy, + no_phase: true + ] + import Helper def up(operation) do - AddCustomIndex.down(operation) + AddCustomIndex.down(%{operation | multitenancy: operation.old_multitenancy}) end def down(operation) do - AddCustomIndex.up(operation) + AddCustomIndex.up(%{operation | multitenancy: operation.old_multitenancy}) end end defmodule RemoveReferenceIndex do @moduledoc false - defstruct [:schema, :table, :source, :multitenancy, no_phase: true] + defstruct [:schema, :table, :source, :multitenancy, :old_multitenancy, no_phase: true] import Helper def up(operation) do - AddReferenceIndex.down(operation) + AddReferenceIndex.down(%{operation | multitenancy: operation.old_multitenancy}) end def down(operation) do - AddReferenceIndex.up(operation) + AddReferenceIndex.up(%{operation | multitenancy: operation.old_multitenancy}) end end From 64c3c82f0910db410441a9a5d63a388e736b74a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:48:10 -0400 Subject: [PATCH 1011/1215] chore(deps): bump igniter in the production-dependencies group (#537) Bumps the production-dependencies group with 1 update: [igniter](https://github.com/ash-project/igniter). Updates `igniter` from 0.5.46 to 0.5.47 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.46...v0.5.47) --- updated-dependencies: - dependency-name: igniter dependency-version: 0.5.47 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index e282ca96..ee0a5788 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.46", "e3ad5b07a194b6e550ddd303bac45a126a65c6157c8acb664b22011cac8e34fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "64c59b696b678b2b83e2ee923f5254ac6479aff6c65dd513383bc0e4cdaeeeb7"}, + "igniter": {:hex, :igniter, "0.5.47", "7a1041d5e38303e526fa6b6de37c9e78013f5cb573833ed51183d18e3a152f10", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "53a900909e20f217a25d15a34fef629c562b4822c1fb39cfa5d6999bc72992ed"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 448ac31756a5e6c130888292526acfa2de767162 Mon Sep 17 00:00:00 2001 From: Dmitry Maganov Date: Mon, 28 Apr 2025 15:03:15 -0600 Subject: [PATCH 1012/1215] fix: fix some issues in migration generator related to tenancy (#539) --- lib/migration_generator/operation.ex | 92 ++++++---------------------- 1 file changed, 18 insertions(+), 74 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 475b88e5..62b3b3a6 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -127,6 +127,14 @@ defmodule AshPostgres.MigrationGenerator.Operation do end def match_type(_), do: nil + + def index_keys(keys, all_tenants?, multitenancy) do + if multitenancy.strategy == :attribute and not all_tenants? do + [multitenancy.attribute | keys] + else + keys + end + end end defmodule CreateTable do @@ -851,18 +859,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do schema: schema, multitenancy: multitenancy }) do - keys = - if all_tenants? do - keys - else - case multitenancy.strategy do - :attribute -> - [multitenancy.attribute | keys] - - _ -> - keys - end - end + keys = index_keys(keys, all_tenants?, multitenancy) index_name = index_name || "#{table}_#{name}_index" @@ -888,19 +885,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do end def down(%{ - identity: %{name: name, keys: keys, index_name: index_name}, + identity: %{name: name, keys: keys, index_name: index_name, all_tenants?: all_tenants?}, table: table, schema: schema, multitenancy: multitenancy }) do - keys = - case multitenancy.strategy do - :attribute -> - [multitenancy.attribute | keys] - - _ -> - keys - end + keys = index_keys(keys, all_tenants?, multitenancy) index_name = index_name || "#{table}_#{name}_index" @@ -962,12 +952,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do base_filter: base_filter, multitenancy: multitenancy }) do - keys = - if !index.all_tenants? and multitenancy.strategy == :attribute do - [multitenancy.attribute | index.fields] - else - index.fields - end + keys = index_keys(index.fields, index.all_tenants?, multitenancy) index = case {index.where, base_filter} do @@ -997,12 +982,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do end def down(%{schema: schema, index: index, table: table, multitenancy: multitenancy}) do - keys = - if !index.all_tenants? and multitenancy.strategy == :attribute do - [multitenancy.attribute | index.fields] - else - index.fields - end + keys = index_keys(index.fields, index.all_tenants?, multitenancy) opts = join([ @@ -1167,7 +1147,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do @moduledoc false defstruct [:schema, :table, no_phase: true] - def up(%{schema: schema, table: table, multitenancy: multitenancy}) do + def up(%{schema: schema, table: table, old_multitenancy: multitenancy}) do cond do multitenancy.strategy == :context -> "drop constraint(#{inspect(table)}, \"#{table}_pkey\", prefix: prefix())" @@ -1305,48 +1285,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do import Helper - def up(%{ - identity: %{name: name, keys: keys, index_name: index_name}, - table: table, - schema: schema, - old_multitenancy: multitenancy - }) do - keys = - case multitenancy.strategy do - :attribute -> - [multitenancy.attribute | keys] - - _ -> - keys - end - - index_name = index_name || "#{table}_#{name}_index" - - "drop_if_exists unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})" + def up(operation) do + AddUniqueIndex.down(%{operation | multitenancy: operation.old_multitenancy}) end - def down(%{ - identity: %{name: name, keys: keys, base_filter: base_filter, index_name: index_name}, - table: table, - schema: schema, - multitenancy: multitenancy - }) do - keys = - case multitenancy.strategy do - :attribute -> - [multitenancy.attribute | keys] - - _ -> - keys - end - - index_name = index_name || "#{table}_#{name}_index" - - if base_filter do - "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], where: \"#{base_filter}\", #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})" - else - "create unique_index(:#{as_atom(table)}, [#{Enum.map_join(keys, ", ", &inspect/1)}], #{join(["name: \"#{index_name}\"", option(:prefix, schema)])})" - end + def down(operation) do + AddUniqueIndex.up(%{operation | multitenancy: operation.old_multitenancy}) end end From eed58bab9a86b1da4cde4f62cb1959bbaca81bc9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 29 Apr 2025 12:33:59 -0400 Subject: [PATCH 1013/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index ee0a5788..f91933a6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"}, + "ash": {:hex, :ash, "3.5.7", "bec34ca7c779f034c48df1251c293507c2f1ee707d95656c6b7daed7379bcbbc", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "390b86a3e2f1ef4219d36a254a9a1be279b5387f4a6da551b41f864816e6ea0c"}, "ash_sql": {:hex, :ash_sql, "0.2.72", "3c353fd3361257310864dce131acd93e407379439cf8667d7ddb4cba99a3fe2c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0a802299580b8406258f9d72ec8c2a39b4d4b9ad49a2631d47a15d3290d7b0eb"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.47", "7a1041d5e38303e526fa6b6de37c9e78013f5cb573833ed51183d18e3a152f10", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "53a900909e20f217a25d15a34fef629c562b4822c1fb39cfa5d6999bc72992ed"}, + "igniter": {:hex, :igniter, "0.5.48", "2873c71d344c7ab342d08703668581aa48461bf2ebcbf634d03efc222016e426", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "33b4c6cd884f04f9a6ea8774a2f6b71b4504eadff8b9aa2ca9a98c1c4234c0f9"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"}, - "spark": {:hex, :spark, "2.2.52", "50094275c9bbafa8e5e9eed0ab61983ee209a500e7044914ccf88e9921ae5082", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "f8de8c298bbbf7abd2a80d0ecabcefef65941f397cdbe94ce6165a121b09084f"}, + "spark": {:hex, :spark, "2.2.55", "5a6f6c43fe6528a82bedc397a19561db221f35c64dac8399f5a65c7b14bd15da", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d1a4b295d69ebf49494f9f7c78e5001688888e8e95214c7c4bc2495490330ef3"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 097509c7433afa735d2326cbed1388cce2803cc0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 29 Apr 2025 12:35:04 -0400 Subject: [PATCH 1014/1215] chore: release version v2.5.18 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd05904..4d1f872c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.18](https://github.com/ash-project/ash_postgres/compare/v2.5.17...v2.5.18) (2025-04-29) + + + + +### Bug Fixes: + +* fix some issues in migration generator related to tenancy (#539) + +* use old multitenancy in generated removals of previous indexes (#536) + +* add tenant to ash bindings in update (#534) + +* correct order, when renaming attribute with an identity (#533) + ## [v2.5.17](https://github.com/ash-project/ash_postgres/compare/v2.5.16...v2.5.17) (2025-04-22) diff --git a/mix.exs b/mix.exs index 7fbd4dac..02628a3a 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.17" + @version "2.5.18" def project do [ From f74a60be85619b346aafa4e3aaf9bb0127729f57 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 30 Apr 2025 21:44:49 -0400 Subject: [PATCH 1015/1215] test: add test for https://github.com/ash-project/ash_sql/issues/127 --- test/aggregate_test.exs | 22 ++++++++++++++++++++++ test/support/resources/post.ex | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index efae1cfa..bd34f579 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1025,6 +1025,28 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end + @tag :regression + test "aggregates with parent expressions in their filters are not grouped" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "title"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "something else"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + assert %{count_of_comments: 2, count_of_comments_with_same_name: 1} = + post + |> Ash.load!([:count_of_comments, :count_of_comments_with_same_name]) + end + describe "sum" do test "with no related data it returns nil" do post = diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 4c251555..6ddcb160 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -896,6 +896,11 @@ defmodule AshPostgres.Test.Post do aggregates do sum(:sum_of_comment_ratings_calc, [:comments, :ratings], :double_score) count(:count_of_comments, :comments) + + count :count_of_comments_with_same_name, :comments do + filter(expr(title == parent(title))) + end + count(:count_of_linked_posts, :linked_posts) count :count_of_comments_called_match, :comments do From 13b67c6c3c25b1ff2cd3446dbafd73d32549d076 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 30 Apr 2025 21:48:00 -0400 Subject: [PATCH 1016/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index f91933a6..9f049d0e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.7", "bec34ca7c779f034c48df1251c293507c2f1ee707d95656c6b7daed7379bcbbc", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "390b86a3e2f1ef4219d36a254a9a1be279b5387f4a6da551b41f864816e6ea0c"}, - "ash_sql": {:hex, :ash_sql, "0.2.72", "3c353fd3361257310864dce131acd93e407379439cf8667d7ddb4cba99a3fe2c", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0a802299580b8406258f9d72ec8c2a39b4d4b9ad49a2631d47a15d3290d7b0eb"}, + "ash": {:hex, :ash, "3.5.8", "8c9fbc72b9739cd4659595f87685039a3ee373d87933399a6c14596962898989", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9a5196152b526795b1650972621b50c3ee0b45d10d6b200dd70e50e7407eb31"}, + "ash_sql": {:hex, :ash_sql, "0.2.74", "f1e1effeb402c2e27680b9629b7ac5f6639474b4f8c074402209bd76ff07f56e", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "16a1e57cc0a6616229630f2dae5be1f4e54d05ef87ad29d073174f15bbcf0edf"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.48", "2873c71d344c7ab342d08703668581aa48461bf2ebcbf634d03efc222016e426", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "33b4c6cd884f04f9a6ea8774a2f6b71b4504eadff8b9aa2ca9a98c1c4234c0f9"}, + "igniter": {:hex, :igniter, "0.5.49", "625bfd1cb8886a3fb729ea67515618e06fc890ef438baca56e5f3a12449510f0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a332d5700116d12517d4c2ddce225f0337429fd8cb2cb857dd530a720fa5df3b"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, From 6c67749e674a021adbf6366c0c8f8efb6f62dd3a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 2 May 2025 14:46:54 -0400 Subject: [PATCH 1017/1215] chore: fix manual relationships example closes #540 --- documentation/topics/advanced/manual-relationships.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index 62291adb..8e28bc2d 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -122,6 +122,9 @@ defmodule MyApp.Employee.ManagedEmployees do |> where([l], l.manager_id in ^employee_ids) |> recursive_cte_query("employee_tree", Employee) |> Repo.all() + |> Map.new(fn employee -> + {employee.id, employee} + end) employees |> with_descendants(all_descendants) From c9269f6b8ef0bcdab3d8bef092e14007c6cd5910 Mon Sep 17 00:00:00 2001 From: user20230119 <123089799+user20230119@users.noreply.github.com> Date: Fri, 2 May 2025 16:19:43 -0400 Subject: [PATCH 1018/1215] docs: edit manual relationships example with group_by and relationship name (#542) --- .../topics/advanced/manual-relationships.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index 8e28bc2d..2da54328 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -114,7 +114,9 @@ defmodule MyApp.Employee.ManagedEmployees do @impl true @spec load([Employee.t()], keyword, map) :: {:ok, %{Ash.UUID.t() => [Employee.t()]}} | {:error, any} - def load(employees, _opts, _context) do + def load(employees, _opts, context) do + relationship_name = context.relationship.name + employee_ids = Enum.map(employees, & &1.id) all_descendants = @@ -122,23 +124,21 @@ defmodule MyApp.Employee.ManagedEmployees do |> where([l], l.manager_id in ^employee_ids) |> recursive_cte_query("employee_tree", Employee) |> Repo.all() - |> Map.new(fn employee -> - {employee.id, employee} - end) + |> Enum.group_by(& &1.manager_id, & &1) employees - |> with_descendants(all_descendants) - |> Map.new(&{&1.id, &1.descendants}) + |> with_descendants(all_descendants, relationship_name) + |> Map.new(&{&1.id, Map.get(&1, relationship_name)}) |> then(&{:ok, &1}) end - defp with_descendants([], _), do: [] + defp with_descendants([], _, _), do: [] - defp with_descendants(employees, all_descendants) do + defp with_descendants(employees, all_descendants, relationship_name) do Enum.map(employees, fn employee -> descendants = Map.get(all_descendants, employee.id, []) - %{employee | descendants: with_descendants(descendants, all_descendants)} + Map.put(employee, relationship_name, with_descendants(descendants, all_descendants, relationship_name)) end) end From 3205706322b064d039d31613abd8d7eb351ee8af Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 2 May 2025 21:04:25 -0400 Subject: [PATCH 1019/1215] improvement: support unions (#543) --- lib/data_layer.ex | 48 +++- mix.exs | 6 +- mix.lock | 6 +- test/combination_test.exs | 393 +++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 30 +++ 5 files changed, 470 insertions(+), 13 deletions(-) create mode 100644 test/combination_test.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3d5f7275..e8d9ca03 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -615,6 +615,8 @@ defmodule AshPostgres.DataLayer do @impl true def can?(_, :async_engine), do: true + def can?(_, :combine), do: true + def can?(_, {:combine, _}), do: true def can?(_, :bulk_create), do: true def can?(_, :action_select), do: true @@ -781,12 +783,24 @@ defmodule AshPostgres.DataLayer do repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, query) with_savepoint(repo, query, fn -> - {:ok, - repo.all( - query, - AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, resource) - ) - |> AshSql.Query.remap_mapped_fields(query)} + repo.all( + query, + AshSql.repo_opts(repo, AshPostgres.SqlImplementation, nil, nil, resource) + ) + |> AshSql.Query.remap_mapped_fields(query) + |> then(fn results -> + if query.__ash_bindings__.context[:data_layer][:combination_of_queries?] do + Enum.map(results, fn result -> + struct(resource, result) + |> Map.put(:__meta__, %Ecto.Schema.Metadata{ + state: :loaded + }) + end) + else + results + end + end) + |> then(&{:ok, &1}) end) end rescue @@ -1423,6 +1437,11 @@ defmodule AshPostgres.DataLayer do AshSql.Query.resource_to_query(resource, AshPostgres.SqlImplementation, domain) end + @impl true + def combination_of(combination_of, resource, domain) do + AshSql.Query.combination_of(combination_of, resource, domain, AshPostgres.SqlImplementation) + end + @impl true def update_query(query, changeset, resource, options) do repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, changeset) @@ -1627,7 +1646,7 @@ defmodule AshPostgres.DataLayer do needs_to_join? = requires_adding_inner_join? || query.distinct || - query.limit || query.offset || has_exists? + query.limit || query.offset || has_exists? || query.combinations != [] query = if needs_to_join? do @@ -3253,7 +3272,20 @@ defmodule AshPostgres.DataLayer do @impl true def select(query, select, _resource) do - {:ok, from(row in query, select: struct(row, ^Enum.uniq(select)))} + if query.__ash_bindings__.context[:data_layer][:combination_query?] || + query.__ash_bindings__.context[:data_layer][:combination_of_queries?] do + binding = query.__ash_bindings__.root_binding + + query = + from(row in Ecto.Query.exclude(query, :select), select: %{}) + + Enum.reduce(select, query, fn field, query -> + from(row in query, select_merge: %{^field => field(as(^binding), ^field)}) + end) + |> then(&{:ok, &1}) + else + {:ok, from(row in query, select: struct(row, ^Enum.uniq(select)))} + end end @impl true diff --git a/mix.exs b/mix.exs index 02628a3a..67a6d34f 100644 --- a/mix.exs +++ b/mix.exs @@ -166,8 +166,10 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, + # {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, + {:ash, ash_version(github: "ash-project/ash")}, + {:ash_sql, ash_sql_version(github: "ash-project/ash_sql")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index 9f049d0e..c24b1dc8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.8", "8c9fbc72b9739cd4659595f87685039a3ee373d87933399a6c14596962898989", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9a5196152b526795b1650972621b50c3ee0b45d10d6b200dd70e50e7407eb31"}, - "ash_sql": {:hex, :ash_sql, "0.2.74", "f1e1effeb402c2e27680b9629b7ac5f6639474b4f8c074402209bd76ff07f56e", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "16a1e57cc0a6616229630f2dae5be1f4e54d05ef87ad29d073174f15bbcf0edf"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "ef0a5193d142e004bb5af27c11c8a4e352cff478", []}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "4cc9f2af6385300d14b51a5b104cd1ec64bed6ae", []}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.49", "625bfd1cb8886a3fb729ea67515618e06fc890ef438baca56e5f3a12449510f0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a332d5700116d12517d4c2ddce225f0337429fd8cb2cb857dd530a720fa5df3b"}, + "igniter": {:hex, :igniter, "0.5.50", "2f6f3a50e02835e961b6228bfcdebe96cd6e9371042939e7f080c83049057e57", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "2e992df458c044f3a18ff6347275743b21092d6677368fdb8dfded321b85cc7b"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, diff --git a/test/combination_test.exs b/test/combination_test.exs new file mode 100644 index 00000000..e5156ca2 --- /dev/null +++ b/test/combination_test.exs @@ -0,0 +1,393 @@ +defmodule AshPostgres.CombinationTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Post + + require Ash.Query + import Ash.Expr + + describe "combinations in actions" do + test "with no data" do + Post + |> Ash.Query.for_read(:first_and_last_post) + |> Ash.read!() + end + + test "with data" do + Post + |> Ash.Changeset.for_create(:create, %{title: "title1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title3"}) + |> Ash.create!() + + assert [%{title: "title1"}, %{title: "title3"}] = + Post + |> Ash.Query.for_read(:first_and_last_post) + |> Ash.read!() + end + + test "with data and sort" do + Post + |> Ash.Changeset.for_create(:create, %{title: "title1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title3"}) + |> Ash.create!() + + assert [%{title: "title3"}, %{title: "title1"}] = + Post + |> Ash.Query.for_read(:first_and_last_post) + |> Ash.Query.sort(title: :desc) + |> Ash.read!() + end + + test "with data and sort, limit and filter" do + Post + |> Ash.Changeset.for_create(:create, %{title: "title1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title3"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title4"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "title5"}) + |> Ash.create!() + + assert ["title5", "title4", "title1"] = + Post + |> Ash.Query.for_read(:first_and_last_two_posts) + |> Ash.Query.sort(title: :desc) + |> Ash.Query.filter(title in ["title4", "title5", "title1"]) + |> Ash.Query.limit(3) + |> Ash.read!() + |> Enum.map(& &1.title) + + assert ["title5", "title4", "title2"] = + Post + |> Ash.Query.for_read(:first_and_last_two_posts) + |> Ash.Query.sort(title: :desc) + |> Ash.Query.filter(title in ["title4", "title5", "title2"]) + |> Ash.Query.limit(3) + |> Ash.read!() + |> Enum.map(& &1.title) + end + end + + describe "combinations" do + test "it combines multiple queries into one result set" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post4"}) + |> Ash.create!() + + assert [%Post{title: "post4"}, %Post{title: "post1"}] = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(title == "post4"), + limit: 1 + ), + Ash.Query.Combination.union_all( + filter: expr(title == "post1"), + limit: 1 + ) + ]) + |> Ash.read!() + end + + test "you can define computed properties" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3"}) + |> Ash.create!() + + assert [%Post{title: "post3", calculations: %{post_group: 1}}] = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(title == "post3"), + limit: 1, + calculations: %{ + post_group: calc(1, type: :integer), + common_value: calc(1, type: :integer) + } + ), + Ash.Query.Combination.union_all( + filter: expr(title == "post1"), + calculations: %{ + post_group: calc(2, type: :integer), + common_value: calc(1, type: :integer) + }, + limit: 1 + ) + ]) + |> Ash.Query.distinct_sort([{calc(^combinations(:common_value)), :asc}]) + |> Ash.Query.sort([{calc(^combinations(:post_group)), :desc}]) + |> Ash.Query.distinct([{calc(^combinations(:common_value)), :asc}]) + |> Ash.Query.calculate(:post_group, :integer, expr(^combinations(:post_group))) + |> Ash.read!() + end + + test "it handles combinations with intersect" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "shared"}) + |> Ash.create!() + + assert [%Post{title: "shared"}] = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base(filter: expr(title in ["post1", "shared"])), + Ash.Query.Combination.intersect(filter: expr(title in ["post2", "shared"])) + ]) + |> Ash.read!() + end + + test "it handles combinations with except" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "shared"}) + |> Ash.create!() + + result = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base(filter: expr(title in ["post1", "shared"])), + Ash.Query.Combination.except(filter: expr(title == "shared")) + ]) + |> Ash.read!() + + assert length(result) == 1 + assert hd(result).title == "post1" + end + + test "combinations with multiple union_all" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3"}) + |> Ash.create!() + + result = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base(filter: expr(title == "post1")), + Ash.Query.Combination.union_all(filter: expr(title == "post2")), + Ash.Query.Combination.union_all(filter: expr(title == "post3")) + ]) + |> Ash.read!() + + assert length(result) == 3 + assert Enum.any?(result, &(&1.title == "post1")) + assert Enum.any?(result, &(&1.title == "post2")) + assert Enum.any?(result, &(&1.title == "post3")) + end + + test "combination with offset" do + # Create posts with increasing title numbers for predictable sort order + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3"}) + |> Ash.create!() + + result = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(contains(title, "post")), + offset: 1, + limit: 2, + sort: [title: :asc] + ) + ]) + |> Ash.read!() + + assert length(result) == 2 + assert hd(result).title == "post2" + assert List.last(result).title == "post3" + end + + test "combinations with complex calculations" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + result = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(title == "post1"), + calculations: %{ + prefix: calc("first", type: :string), + full_title: calc("first-" <> title, type: :string) + } + ), + Ash.Query.Combination.union_all( + filter: expr(title == "post2"), + calculations: %{ + prefix: calc("second", type: :string), + full_title: calc("second-" <> title, type: :string) + } + ) + ]) + |> Ash.Query.calculate(:title_prefix, :string, expr(^combinations(:prefix))) + |> Ash.Query.calculate(:display_title, :string, expr(^combinations(:full_title))) + |> Ash.read!() + + post1 = Enum.find(result, &(&1.title == "post1")) + post2 = Enum.find(result, &(&1.title == "post2")) + + assert post1.calculations.title_prefix == "first" + assert post1.calculations.display_title == "first-post1" + assert post2.calculations.title_prefix == "second" + assert post2.calculations.display_title == "second-post2" + end + + test "combinations with sorting by calculation" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3"}) + |> Ash.create!() + + result = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base(calculations: %{sort_order: calc(3, type: :integer)}), + Ash.Query.Combination.union_all( + filter: expr(title == "post2"), + calculations: %{sort_order: calc(1, type: :integer)} + ), + Ash.Query.Combination.union_all( + filter: expr(title == "post3"), + calculations: %{sort_order: calc(2, type: :integer)} + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :asc}, {:title, :asc}]) + |> Ash.Query.distinct(:title) + |> Ash.read!() + + assert [first, second, third | _] = result + assert first.title == "post2" + assert second.title == "post3" + assert third.title == "post1" + end + + test "combination with distinct" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1", score: 10}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2", score: 10}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3", score: 20}) + |> Ash.create!() + + result = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(score == 10), + select: [:id, :score], + calculations: %{score_group: calc("low", type: :string)} + ), + Ash.Query.Combination.union_all( + filter: expr(score == 20), + select: [:id, :score], + calculations: %{score_group: calc("high", type: :string)} + ) + ]) + |> Ash.Query.distinct([{calc(^combinations(:score_group)), :asc}]) + |> Ash.Query.calculate(:upper_title, :string, expr(fragment("UPPER(?)", title))) + |> Ash.read!() + + assert Enum.all?(result, &(&1.calculations.upper_title == String.upcase(&1.title))) + + # Should only have 2 results since we're distinct on score group + assert length(result) == 2 + + groups = + Enum.map(result, & &1.calculations[:score_group]) + + assert "low" in groups + assert "high" in groups + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6ddcb160..e459cfa1 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -154,6 +154,36 @@ defmodule AshPostgres.Test.Post do defaults([:read, :destroy]) + read :first_and_last_post do + prepare(fn query, _ -> + Ash.Query.combination_of(query, [ + Ash.Query.Combination.base( + limit: 1, + sort: [created_at: :desc] + ), + Ash.Query.Combination.union( + limit: 1, + sort: [created_at: :asc] + ) + ]) + end) + end + + read :first_and_last_two_posts do + prepare(fn query, _ -> + Ash.Query.combination_of(query, [ + Ash.Query.Combination.base( + limit: 2, + sort: [created_at: :desc] + ), + Ash.Query.Combination.union( + limit: 2, + sort: [created_at: :asc] + ) + ]) + end) + end + update :add_to_limited_score do argument(:amount, :integer, allow_nil?: false) change(atomic_update(:limited_score, expr((limited_score || 0) + ^arg(:amount)))) From 85790977ddb4323ada6081da0a9dafa4f9a2daf2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 3 May 2025 02:52:01 -0400 Subject: [PATCH 1020/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c24b1dc8..cfdbd288 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:git, "https://github.com/ash-project/ash.git", "ef0a5193d142e004bb5af27c11c8a4e352cff478", []}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "4cc9f2af6385300d14b51a5b104cd1ec64bed6ae", []}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "6a8fd8559a2a7ecaba439d88101a76ff85f72d31", []}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From 7302636e77e4b6818027038a2beafb64d40c1ddb Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 6 May 2025 15:46:33 +0200 Subject: [PATCH 1021/1215] test: that shows that parent stacking works in general but not with filter (#544) --- test/rel_with_parent_filter_test.exs | 46 ++++++++++++++++++++++++++++ test/support/resources/post.ex | 6 ++++ 2 files changed, 52 insertions(+) diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs index 20a4082e..d8fa5161 100644 --- a/test/rel_with_parent_filter_test.exs +++ b/test/rel_with_parent_filter_test.exs @@ -2,6 +2,7 @@ defmodule AshPostgres.RelWithParentFilterTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Author + alias AshPostgres.Test.Post require Ash.Query @@ -75,4 +76,49 @@ defmodule AshPostgres.RelWithParentFilterTest do assert length(authors) == 1 end + + test "can stack parents" do + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "abc" + }) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "the one", author_id: author.id}) + |> Ash.create!() + + assert %{author_from_exists: author_from_exists} = + Post + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(title == "the one") + |> Ash.Query.load(:author_from_exists) + |> Ash.Query.limit(1) + |> Ash.read_one!() + + assert author_from_exists.id == author.id + end + + test "can filter with stack parents" do + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "abc" + }) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "the one", author_id: author.id}) + |> Ash.create!() + + assert %{author_from_exists: author_from_exists} = + Post + |> Ash.Query.for_read(:read) + |> Ash.Query.filter(author_from_exists.first_name == "abc") + |> Ash.Query.load(:author_from_exists) + |> Ash.read_one!() + + assert author_from_exists.id == author.id + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e459cfa1..b6f4e3a5 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -559,6 +559,12 @@ defmodule AshPostgres.Test.Post do public?(true) end + has_one :author_from_exists, AshPostgres.Test.Author do + public?(true) + no_attributes?(true) + filter(expr(exists(posts, id == parent(parent(id))))) + end + has_many :co_author_posts, AshPostgres.Test.CoAuthorPost do public?(true) From 2701fc2017d5bc0a7e04edae770f020d4f500cfa Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 6 May 2025 09:48:23 -0400 Subject: [PATCH 1022/1215] chore: update deps --- mix.exs | 6 ++---- mix.lock | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 67a6d34f..02628a3a 100644 --- a/mix.exs +++ b/mix.exs @@ -166,10 +166,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, - {:ash, ash_version(github: "ash-project/ash")}, - {:ash_sql, ash_sql_version(github: "ash-project/ash_sql")}, + {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, diff --git a/mix.lock b/mix.lock index cfdbd288..22e5f67d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "ef0a5193d142e004bb5af27c11c8a4e352cff478", []}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "6a8fd8559a2a7ecaba439d88101a76ff85f72d31", []}, + "ash": {:hex, :ash, "3.5.9", "4956f2b0056524757ba23f335f452ce88e8d21daea2c347bfc1c287fdaca7b67", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a60a6098b23f65cbba54eb376a51f9f0d5937930a9e57999d83cfe7f8ada52ee"}, + "ash_sql": {:hex, :ash_sql, "0.2.75", "94252b460db2e14b778fa542684811c48fa08b4b2a2916db6a05a33e4272c361", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "966226cd5b368258d05359c9a3f37cf86b412f5822a59c85ac27f047ca9e95cb"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From 8d412ce8f458a53f5c2724358bc7cf6c52cb9e3f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 6 May 2025 09:49:05 -0400 Subject: [PATCH 1023/1215] chore: release version v2.5.19 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1f872c..1f5bc056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.19](https://github.com/ash-project/ash_postgres/compare/v2.5.18...v2.5.19) (2025-05-06) + + + + +### Improvements: + +* support unions (#543) + ## [v2.5.18](https://github.com/ash-project/ash_postgres/compare/v2.5.17...v2.5.18) (2025-04-29) diff --git a/mix.exs b/mix.exs index 02628a3a..d0f3950c 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.18" + @version "2.5.19" def project do [ From e963ea0ccdcf2c5733be674d99279877b09d42dc Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 9 May 2025 23:31:13 -0400 Subject: [PATCH 1024/1215] chore: fix warnings around installers --- lib/mix/tasks/ash_postgres.gen.resources.ex | 7 +-- lib/mix/tasks/ash_postgres.install.ex | 17 +++--- lib/mix/tasks/ash_postgres.setup_vector.ex | 66 +++++++++++++++++++++ 3 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 lib/mix/tasks/ash_postgres.setup_vector.ex diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 9a36d7a9..2196c2f0 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -77,15 +77,14 @@ if Code.ensure_loaded?(Igniter) do end @impl Igniter.Mix.Task - def igniter(igniter, argv) do + def igniter(igniter) do Mix.Task.run("compile") - {%{domain: domain}, argv} = positional_args!(argv) + options = igniter.args.options + domain = igniter.args.positional.domain domain = Igniter.Project.Module.parse(domain) - options = options!(argv) - repos = case options[:repo] do [] -> diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 364237dd..73f50db0 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -21,8 +21,9 @@ if Code.ensure_loaded?(Igniter) do end @impl true - def igniter(igniter, argv) do - opts = options!(argv) + def igniter(igniter) do + argv = igniter.args.argv + opts = igniter.args.options repo = case opts[:repo] do @@ -465,22 +466,22 @@ if Code.ensure_loaded?(Igniter) do notice = if min_pg_version do """ - A `min_pg_version/0` function has been defined + A `min_pg_version/0` function has been defined in `#{inspect(repo)}` as `#{min_pg_version}`. This was based on running `postgres -V`. - You may wish to update this configuration. It should - be set to the lowest version that your application + You may wish to update this configuration. It should + be set to the lowest version that your application expects to be run against. """ else """ - A `min_pg_version/0` function has been defined in + A `min_pg_version/0` function has been defined in `#{inspect(repo)}` automatically. - You may wish to update this configuration. It should - be set to the lowest version that your application + You may wish to update this configuration. It should + be set to the lowest version that your application expects to be run against. """ end diff --git a/lib/mix/tasks/ash_postgres.setup_vector.ex b/lib/mix/tasks/ash_postgres.setup_vector.ex new file mode 100644 index 00000000..c58b6f56 --- /dev/null +++ b/lib/mix/tasks/ash_postgres.setup_vector.ex @@ -0,0 +1,66 @@ +defmodule Mix.Tasks.AshPostgres.SetupVector.Docs do + @moduledoc false + + def short_doc do + "Sets up pgvector for AshPostgres" + end + + def example do + "mix ash_postgres.setup_vector" + end + + def long_doc do + """ + #{short_doc()} + + ## Example + + ```bash + #{example()} + ``` + """ + end +end + +if Code.ensure_loaded?(Igniter) do + defmodule Mix.Tasks.AshPostgres.SetupVector do + @shortdoc "#{__MODULE__.Docs.short_doc()}" + + @moduledoc __MODULE__.Docs.long_doc() + + use Igniter.Mix.Task + + @impl Igniter.Mix.Task + def info(_argv, _composing_task) do + %Igniter.Mix.Task.Info{ + group: :ash_postgres, + example: __MODULE__.Docs.example() + } + end + + @impl Igniter.Mix.Task + def igniter(igniter) do + # Do your work here and return an updated igniter + igniter + # |> Igniter.add_warning("mix ash_postgres.setup_vector is not yet implemented") + end + end +else + defmodule Mix.Tasks.AshPostgres.SetupVector do + @shortdoc "#{__MODULE__.Docs.short_doc()} | Install `igniter` to use" + + @moduledoc __MODULE__.Docs.long_doc() + + use Mix.Task + + def run(_argv) do + Mix.shell().error(""" + The task 'ash_postgres.setup_vector' requires igniter. Please install igniter and try again. + + For more information, see: https://hexdocs.pm/igniter/readme.html#installation + """) + + exit({:shutdown, 1}) + end + end +end From 578a63a8413ea51c6a7bae413e9fec163285ac22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 13:20:08 +0200 Subject: [PATCH 1025/1215] chore(deps): bump the production-dependencies group with 2 updates (#546) Bumps the production-dependencies group with 2 updates: [ash](https://github.com/ash-project/ash) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.5.9 to 3.5.10 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.9...v3.5.10) Updates `igniter` from 0.5.50 to 0.5.51 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.5.50...v0.5.51) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-version: 0.5.51 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 22e5f67d..38ebed66 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.9", "4956f2b0056524757ba23f335f452ce88e8d21daea2c347bfc1c287fdaca7b67", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a60a6098b23f65cbba54eb376a51f9f0d5937930a9e57999d83cfe7f8ada52ee"}, + "ash": {:hex, :ash, "3.5.10", "123d5f5840326e2aa60204c9dc74b43c1eb35258bffb630d3f4190ee2a123ef1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "79217d9017bdc3ce5e32f429b9f32b03ef77e07337ccf41f95ac79fe0b3851e0"}, "ash_sql": {:hex, :ash_sql, "0.2.75", "94252b460db2e14b778fa542684811c48fa08b4b2a2916db6a05a33e4272c361", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "966226cd5b368258d05359c9a3f37cf86b412f5822a59c85ac27f047ca9e95cb"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.50", "2f6f3a50e02835e961b6228bfcdebe96cd6e9371042939e7f080c83049057e57", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "2e992df458c044f3a18ff6347275743b21092d6677368fdb8dfded321b85cc7b"}, + "igniter": {:hex, :igniter, "0.5.51", "21c3f9b425bd88572034644781057a793205b401ffd09e1c9498c528f14ef335", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9f1468d156145b463c6649c61e542befbf75f9d76055456d05c5f6adf7cfb272"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -31,7 +31,7 @@ "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, - "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, @@ -44,8 +44,8 @@ "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"}, - "spark": {:hex, :spark, "2.2.55", "5a6f6c43fe6528a82bedc397a19561db221f35c64dac8399f5a65c7b14bd15da", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d1a4b295d69ebf49494f9f7c78e5001688888e8e95214c7c4bc2495490330ef3"}, + "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, + "spark": {:hex, :spark, "2.2.56", "9426f6b992b13cd2a83ba3567d7a9f8cf8e2f264d019983da12882b43351c9b6", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "ecdc66a91bfd9155047c4f36d594fdf7e72c5f721b451e5aa6967c5439fa2b65"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 4651aa646530aaa19dd58dd24d46fb26e6183ad8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 13:24:46 +0200 Subject: [PATCH 1026/1215] chore(deps-dev): bump the dev-dependencies group with 2 updates (#547) Bumps the dev-dependencies group with 2 updates: [ex_doc](https://github.com/elixir-lang/ex_doc) and [sobelow](https://github.com/sobelow/sobelow). Updates `ex_doc` from 0.37.3 to 0.38.1 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.3...v0.38.1) Updates `sobelow` from 0.13.0 to 0.14.0 - [Changelog](https://github.com/sobelow/sobelow/blob/main/CHANGELOG.md) - [Commits](https://github.com/sobelow/sobelow/commits) --- updated-dependencies: - dependency-name: ex_doc dependency-version: 0.38.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: sobelow dependency-version: 0.14.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 38ebed66..2805978e 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"}, + "ex_doc": {:hex, :ex_doc, "0.38.1", "bae0a0bd5b5925b1caef4987e3470902d072d03347114ffe03a55dbe206dd4c2", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "754636236d191b895e1e4de2ebb504c057fe1995fdfdd92e9d75c4b05633008b"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, @@ -43,7 +43,7 @@ "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, - "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, + "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spark": {:hex, :spark, "2.2.56", "9426f6b992b13cd2a83ba3567d7a9f8cf8e2f264d019983da12882b43351c9b6", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "ecdc66a91bfd9155047c4f36d594fdf7e72c5f721b451e5aa6967c5439fa2b65"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, From cc6929d98a2cf148311ad78a763c820e709987f5 Mon Sep 17 00:00:00 2001 From: Marc Planelles Date: Mon, 19 May 2025 07:35:47 +0200 Subject: [PATCH 1027/1215] docs: add example all_tenants implementation (#548) --- .../topics/advanced/schema-based-multitenancy.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/documentation/topics/advanced/schema-based-multitenancy.md b/documentation/topics/advanced/schema-based-multitenancy.md index 2393ebaa..69d7add1 100644 --- a/documentation/topics/advanced/schema-based-multitenancy.md +++ b/documentation/topics/advanced/schema-based-multitenancy.md @@ -10,6 +10,22 @@ The generated migrations include a lot of niceties around multitenancy. Specific Migrations in the tenant directory will call `repo().all_tenants()`, which is a callback you will need to implement in your repo that should return a list of all schemas that need to be migrated. +For example, if you use the `manage_tenant` directive described below, you could do: + +```elixir +defmodule Myapp.Repo do + use AshPostgres.Repo, ... + + import Ecto.Query, only: [from: 2] + + ... + + def all_tenants do + all(from(row in "organizations", select: fragment("? || ?", "org_", row.id))) + end +end +``` + ## Automatically managing tenants By setting the `template` configuration, in the `manage_tenant` section, you can cause the creation/updating of a given resource to create/rename tenants. For example: From dcdafd9ea9735cec0bb2c6098789b0aec4bad4c5 Mon Sep 17 00:00:00 2001 From: Marc Planelles Date: Mon, 19 May 2025 22:59:03 +0200 Subject: [PATCH 1028/1215] fix: enforce tenant name rules at creation (#550) Closes #549 --- lib/multitenancy.ex | 1 + .../20250519103535.json | 29 +++++++++++++ .../20250519103535_migrate_resources53.exs | 19 +++++++++ test/multitenancy_test.exs | 31 +++++++++++++- test/support/multitenancy/domain.ex | 1 + .../multitenancy/resources/named_org.ex | 41 +++++++++++++++++++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json create mode 100644 priv/test_repo/migrations/20250519103535_migrate_resources53.exs create mode 100644 test/support/multitenancy/resources/named_org.ex diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 20e67c0c..6927a960 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -5,6 +5,7 @@ defmodule AshPostgres.MultiTenancy do @tenant_name_regex ~r/^[a-zA-Z0-9_-]+$/ def create_tenant!(tenant_name, repo) do + validate_tenant_name!(tenant_name) Ecto.Adapters.SQL.query!(repo, "CREATE SCHEMA IF NOT EXISTS \"#{tenant_name}\"", []) migrate_tenant(tenant_name, repo) diff --git a/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json b/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json new file mode 100644 index 00000000..982b3edb --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json @@ -0,0 +1,29 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "name", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "63124167427BA3C61197814348217EFC967CDAA398102552836E26BD93E198C8", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "multitenant_named_orgs" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250519103535_migrate_resources53.exs b/priv/test_repo/migrations/20250519103535_migrate_resources53.exs new file mode 100644 index 00000000..6059ae3e --- /dev/null +++ b/priv/test_repo/migrations/20250519103535_migrate_resources53.exs @@ -0,0 +1,19 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources53 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 + create table(:multitenant_named_orgs, primary_key: false) do + add(:name, :text, null: false, primary_key: true) + end + end + + def down do + drop(table(:multitenant_named_orgs)) + end +end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 73f9cb52..0dd99520 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -2,7 +2,7 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false require Ash.Query - alias AshPostgres.MultitenancyTest.{CompositeKeyPost, Org, Post, User} + alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User} alias AshPostgres.Test.Post, as: GlobalPost setup do @@ -292,4 +292,33 @@ defmodule AshPostgres.Test.MultitenancyTest do |> Ash.create!() end end + + test "rejects characters other than alphanumericals, - and _ on tenant creation" do + assert_raise( + Ash.Error.Unknown, + ~r/Tenant name must match ~r\/\^\[a-zA-Z0-9_-]\+\$\/, got:/, + fn -> + NamedOrg + |> Ash.Changeset.for_create(:create, %{name: "🚫"}) + |> Ash.create!() + end + ) + end + + test "rejects characters other than alphanumericals, - and _ when renaming tenant" do + org = + NamedOrg + |> Ash.Changeset.for_create(:create, %{name: "toto"}) + |> Ash.create!() + + assert_raise( + Ash.Error.Unknown, + ~r/Tenant name must match ~r\/\^\[a-zA-Z0-9_-]\+\$\/, got:/, + fn -> + org + |> Ash.Changeset.for_update(:update, %{name: "🚫"}) + |> Ash.update!() + end + ) + end end diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 85f078da..9ad6f881 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -4,6 +4,7 @@ defmodule AshPostgres.MultitenancyTest.Domain do resources do resource(AshPostgres.MultitenancyTest.Org) + resource(AshPostgres.MultitenancyTest.NamedOrg) resource(AshPostgres.MultitenancyTest.User) resource(AshPostgres.MultitenancyTest.Post) resource(AshPostgres.MultitenancyTest.PostLink) diff --git a/test/support/multitenancy/resources/named_org.ex b/test/support/multitenancy/resources/named_org.ex new file mode 100644 index 00000000..537ea337 --- /dev/null +++ b/test/support/multitenancy/resources/named_org.ex @@ -0,0 +1,41 @@ +defmodule AshPostgres.MultitenancyTest.NamedOrg do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + defimpl Ash.ToTenant do + def to_tenant(%{name: name}, resource) do + if Ash.Resource.Info.data_layer(resource) == AshPostgres.DataLayer && + Ash.Resource.Info.multitenancy_strategy(resource) == :context do + "org_#{name}" + else + name + end + end + end + + attributes do + attribute(:name, :string, + primary_key?: true, + allow_nil?: false, + public?: true, + writable?: true + ) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + postgres do + table "multitenant_named_orgs" + repo(AshPostgres.TestRepo) + + manage_tenant do + template(["org_", :name]) + end + end +end From 391b8cb5bd2c69a0bcf4c1032492d60ca78c250e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 19 May 2025 16:22:04 +0200 Subject: [PATCH 1029/1215] fix: self-join if combination queries require more fields In a query like this: ```elixir Post |> Ash.Query.combination_of([ Ash.Query.Combination.base( filter: expr(score == 10), select: [:id, :score], ), Ash.Query.Combination.union_all( filter: expr(score == 20), select: [:id, :score], ) ]) |> Ash.Query.filter(category == "category1") |> Ash.read!() ``` you'd get a SQL query something like this (before my fix): ```sql SELECT id, score FROM ( SELECT id, score FROM posts WHERE score = 10 UNION ALL SELECT id, score FROM posts WHERE score = 20 ) AS posts WHERE posts.category = "category1" ``` Which wouldn't work because `category` is not a part of the inner selects. Now we add a `JOIN` on primary key to the table, and then include all the fields we're missing from that table, in any conditions that we would reference a field that wasn't selected in the combinations. --- lib/data_layer.ex | 123 +++++++++++++++++++++++++++++++++++++- test/combination_test.exs | 45 ++++++++++++-- 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e8d9ca03..5383ac26 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3262,6 +3262,8 @@ defmodule AshPostgres.DataLayer do @impl true def sort(query, sort, _resource) do + query = maybe_subquery_upgrade(query, {:sort, sort}) + {:ok, Map.update!( query, @@ -3272,12 +3274,18 @@ defmodule AshPostgres.DataLayer do @impl true def select(query, select, _resource) do + query = maybe_subquery_upgrade(query, {:select, select}) + if query.__ash_bindings__.context[:data_layer][:combination_query?] || query.__ash_bindings__.context[:data_layer][:combination_of_queries?] do binding = query.__ash_bindings__.root_binding - query = - from(row in Ecto.Query.exclude(query, :select), select: %{}) + {query, select} = + if field_set = query.__ash_bindings__[:already_selected] do + {query, select -- field_set} + else + {from(row in Ecto.Query.exclude(query, :select), select: %{}), select} + end Enum.reduce(select, query, fn field, query -> from(row in query, select_merge: %{^field => field(as(^binding), ^field)}) @@ -3294,6 +3302,7 @@ defmodule AshPostgres.DataLayer do end def distinct_sort(query, sort, _) do + query = maybe_subquery_upgrade(query, {:distinct_sort, sort}) {:ok, Map.update!(query, :__ash_bindings__, &Map.put(&1, :distinct_sort, sort))} end @@ -3302,11 +3311,13 @@ defmodule AshPostgres.DataLayer do # to come up with alternatives here. @impl true def distinct(query, distinct, resource) do + query = maybe_subquery_upgrade(query, {:distinct, distinct}) AshSql.Distinct.distinct(query, distinct, resource) end @impl true def filter(query, filter, resource, opts \\ []) do + query = maybe_subquery_upgrade(query, {:filter, filter}) used_aggregates = Ash.Filter.used_aggregates(filter, []) query = @@ -3336,6 +3347,112 @@ defmodule AshPostgres.DataLayer do end end + defp maybe_subquery_upgrade( + %{__ash_bindings__: %{subquery_upgrade?: true}} = query, + _ + ) do + query + end + + defp maybe_subquery_upgrade(query, type) do + fieldset = query.__ash_bindings__.context[:data_layer][:combination_fieldset] + + if query.__ash_bindings__.context[:data_layer][:combination_of_queries?] && fieldset do + requires_join? = + case type do + {:filter, contents} -> + Enum.any?( + Ash.Filter.list_refs(contents), + &(&1.relationship_path != [] || &1.attribute.name not in fieldset) + ) + + {:calculations, calculations} -> + Enum.any?(calculations, fn {_, expr} -> + Enum.any?( + Ash.Filter.list_refs(expr), + &(&1.relationship_path != [] || &1.attribute.name not in fieldset) + ) + end) + + {sort, sorts} when sort in [:sort, :distinct, :distinct_sort] -> + Enum.any?(sorts, fn + {atom, _} when is_atom(atom) -> + atom not in fieldset + + {%Ash.Query.Calculation{} = calc, _} -> + calc.opts + |> calc.module.expression(calc.context) + |> Ash.Filter.hydrate_refs(%{ + resource: query.__ash_bindings__.resource, + parent_stack: query.__ash_bindings__[:parent_resources] || [], + public?: false + }) + |> Ash.Filter.list_refs() + |> Enum.any?(&(&1.relationship_path != [] || &1.attribute.name not in fieldset)) + + _ -> + true + end) + + {:select, select} -> + Enum.any?(select, &(&1 not in fieldset)) + end + + resource = query.__ash_bindings__.resource + + if requires_join? do + primary_key = Ash.Resource.Info.primary_key(query.__ash_bindings__.resource) + + if primary_key != [] && primary_key -- fieldset == [] do + dynamic = + Enum.reduce(primary_key, nil, fn key, expr -> + if is_nil(expr) do + Ecto.Query.dynamic([l, r], field(l, ^key) == field(r, ^key)) + else + Ecto.Query.dynamic([l, r], field(l, ^key) == field(r, ^key) and ^expr) + end + end) + + default_select = + MapSet.to_list( + Ash.Resource.Info.selected_by_default_attribute_names( + query.__ash_bindings__.resource + ) + ) + + query_with_select = + from(sub in query, + join: row in ^query.__ash_bindings__.resource, + # why doesn't `.root_binding` work the way I expect it to here? + on: ^dynamic, + select: map(row, ^default_select), + select_merge: map(sub, ^fieldset) + ) + + from(row in subquery(query_with_select), as: ^0) + |> AshSql.Bindings.default_bindings(resource, AshPostgres.SqlImplementation) + |> Map.update!( + :__ash_bindings__, + &Map.merge(&1, %{ + already_selected: fieldset, + subquery_upgrade?: true, + context: query.__ash_bindings__.context + }) + ) + else + raise """ + Unsupported combination query. Combinations must select the primary key if referencing + any fields that are *not* selected by the combinations in filter, sort & distinct. + """ + end + else + query + end + else + query + end + end + @impl true def add_aggregates(query, aggregates, resource) do AshSql.Aggregate.add_aggregates( @@ -3349,6 +3466,8 @@ defmodule AshPostgres.DataLayer do @impl true def add_calculations(query, calculations, resource, select? \\ true) do + query = maybe_subquery_upgrade(query, {:calculations, calculations}) + AshSql.Calculation.add_calculations( query, calculations, diff --git a/test/combination_test.exs b/test/combination_test.exs index e5156ca2..086ae91d 100644 --- a/test/combination_test.exs +++ b/test/combination_test.exs @@ -110,7 +110,7 @@ defmodule AshPostgres.CombinationTest do |> Ash.Changeset.for_create(:create, %{title: "post4"}) |> Ash.create!() - assert [%Post{title: "post4"}, %Post{title: "post1"}] = + assert [%{title: "post4"}, %{title: "post1"}] = Post |> Ash.Query.combination_of([ Ash.Query.Combination.base( @@ -122,7 +122,9 @@ defmodule AshPostgres.CombinationTest do limit: 1 ) ]) + |> Ash.Query.sort(title: :desc) |> Ash.read!() + |> Enum.map(&Map.take(&1, [:title])) end test "you can define computed properties" do @@ -143,6 +145,7 @@ defmodule AshPostgres.CombinationTest do |> Ash.Query.combination_of([ Ash.Query.Combination.base( filter: expr(title == "post3"), + select: [:id], limit: 1, calculations: %{ post_group: calc(1, type: :integer), @@ -151,6 +154,7 @@ defmodule AshPostgres.CombinationTest do ), Ash.Query.Combination.union_all( filter: expr(title == "post1"), + select: [:id], calculations: %{ post_group: calc(2, type: :integer), common_value: calc(1, type: :integer) @@ -158,9 +162,8 @@ defmodule AshPostgres.CombinationTest do limit: 1 ) ]) - |> Ash.Query.distinct_sort([{calc(^combinations(:common_value)), :asc}]) - |> Ash.Query.sort([{calc(^combinations(:post_group)), :desc}]) - |> Ash.Query.distinct([{calc(^combinations(:common_value)), :asc}]) + |> Ash.Query.sort([{calc(^combinations(:post_group)), :asc}]) + |> Ash.Query.distinct([calc(^combinations(:common_value))]) |> Ash.Query.calculate(:post_group, :integer, expr(^combinations(:post_group))) |> Ash.read!() end @@ -389,5 +392,39 @@ defmodule AshPostgres.CombinationTest do assert "low" in groups assert "high" in groups end + + test "combination with filters not included in the field set" do + Post + |> Ash.Changeset.for_create(:create, %{title: "post1", score: 10, category: "category1"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post2", score: 10, category: "category2"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "post3", score: 20, category: "category3"}) + |> Ash.create!() + + assert ["category1"] = + Post + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(score == 10), + select: [:id, :score], + calculations: %{score_group: calc("low", type: :string)} + ), + Ash.Query.Combination.union_all( + filter: expr(score == 20), + select: [:id, :score], + calculations: %{score_group: calc("high", type: :string)} + ) + ]) + |> Ash.Query.filter(category == "category1") + |> Ash.Query.distinct([{calc(^combinations(:score_group)), :asc}]) + |> Ash.Query.calculate(:upper_title, :string, expr(fragment("UPPER(?)", title))) + |> Ash.read!() + |> Enum.map(&to_string(&1.category)) + end end end From 6f287776b9c1324a277d3befc38f06ceacc4dacb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 May 2025 09:35:24 -0400 Subject: [PATCH 1030/1215] test: add tests for tuple type --- .../test_repo/posts/20250520130634.json | 590 ++++++++++++++++++ .../20250520130634_migrate_resources53.exs | 21 + test/support/resources/post.ex | 1 + test/support/types/person_detail.ex | 13 + test/tuple_test.exs | 139 +++++ 5 files changed, 764 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20250520130634.json create mode 100644 priv/test_repo/migrations/20250520130634_migrate_resources53.exs create mode 100644 test/support/types/person_detail.ex create mode 100644 test/tuple_test.exs diff --git a/priv/resource_snapshots/test_repo/posts/20250520130634.json b/priv/resource_snapshots/test_repo/posts/20250520130634.json new file mode 100644 index 00000000..55ae3d4b --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250520130634.json @@ -0,0 +1,590 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "metadata", + "type": "map" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "string_point", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "person_detail", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "points" + }, + "size": null, + "source": "db_point_id", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_string_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "string_points" + }, + "size": null, + "source": "db_string_point_id", + "type": "text" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "28AA0D3E2E9C063A7E09A548781ADFBA9A421B0D29BBAF56F2DE4B828E79CEEB", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250520130634_migrate_resources53.exs b/priv/test_repo/migrations/20250520130634_migrate_resources53.exs new file mode 100644 index 00000000..cff5e211 --- /dev/null +++ b/priv/test_repo/migrations/20250520130634_migrate_resources53.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources53 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(:posts) do + add(:person_detail, :map) + end + end + + def down do + alter table(:posts) do + remove(:person_detail) + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index b6f4e3a5..d2119d2a 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -498,6 +498,7 @@ defmodule AshPostgres.Test.Post do attribute(:point, AshPostgres.Test.Point, public?: true) attribute(:composite_point, AshPostgres.Test.CompositePoint, public?: true) attribute(:string_point, AshPostgres.Test.StringPoint, public?: true) + attribute(:person_detail, AshPostgres.Test.PersonDetail, public?: true) attribute(:stuff, :map, public?: true) attribute(:list_of_stuff, {:array, :map}, public?: true) attribute(:uniq_one, :string, public?: true) diff --git a/test/support/types/person_detail.ex b/test/support/types/person_detail.ex new file mode 100644 index 00000000..551d17d5 --- /dev/null +++ b/test/support/types/person_detail.ex @@ -0,0 +1,13 @@ +defmodule AshPostgres.Test.PersonDetail do + @moduledoc """ + A tuple type for testing Ash.Type.Tuple + """ + use Ash.Type.NewType, + subtype_of: :tuple, + constraints: [ + fields: [ + first_name: [type: :string, allow_nil?: false], + last_name: [type: :string, allow_nil?: false] + ] + ] +end diff --git a/test/tuple_test.exs b/test/tuple_test.exs new file mode 100644 index 00000000..83b412f6 --- /dev/null +++ b/test/tuple_test.exs @@ -0,0 +1,139 @@ +defmodule AshPostgres.Test.TupleTest do + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Post + + require Ash.Query + + test "tuple type can be created with correct values" do + post = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Tuple Test", + person_detail: {"John", "Doe"} + }) + |> Ash.create!() + + assert post.person_detail == {"John", "Doe"} + assert elem(post.person_detail, 0) == "John" + assert elem(post.person_detail, 1) == "Doe" + end + + test "tuple type can be filtered by exact match" do + # Create first post + Post + |> Ash.Changeset.for_create(:create, %{ + title: "First Post", + person_detail: {"John", "Doe"} + }) + |> Ash.create!() + + # Create second post + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Second Post", + person_detail: {"Jane", "Smith"} + }) + |> Ash.create!() + + # Find post with exact tuple match + results = + Post + |> Ash.Query.filter(person_detail == ^{"John", "Doe"}) + |> Ash.read!() + + assert length(results) == 1 + assert hd(results).title == "First Post" + end + + test "tuple type can be filtered by individual fields" do + # Create posts + Post + |> Ash.Changeset.for_create(:create, %{ + title: "John Post 1", + person_detail: {"John", "Doe"} + }) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{ + title: "John Post 2", + person_detail: {"John", "Smith"} + }) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Jane Post", + person_detail: {"Jane", "Doe"} + }) + |> Ash.create!() + + # Filter by equality + results = + Post + |> Ash.Query.filter(person_detail == {"John", "Doe"}) + |> Ash.read!() + + assert length(results) == 1 + + # Filter by first_name + results = + Post + |> Ash.Query.filter(person_detail["first_name"] == "John") + |> Ash.read!() + + assert length(results) == 2 + assert Enum.all?(results, fn post -> elem(post.person_detail, 0) == "John" end) + end + + test "tuple type can be updated" do + # Create post + post = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Original Post", + person_detail: {"Original", "Name"} + }) + |> Ash.create!() + + # Update the post + updated_post = + post + |> Ash.Changeset.for_update(:update, %{ + person_detail: {"Updated", "Name"} + }) + |> Ash.update!() + + assert updated_post.person_detail == {"Updated", "Name"} + + # Verify by reading from database + retrieved_post = + Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.read_one!() + + assert retrieved_post.person_detail == {"Updated", "Name"} + end + + test "tuple type validates constraints" do + # Try to create with empty first name (violates min_length constraint) + assert_raise Ash.Error.Invalid, fn -> + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Invalid Post", + person_detail: {"", "Doe"} + }) + |> Ash.create!() + end + + # Try to create with empty last name (violates min_length constraint) + assert_raise Ash.Error.Invalid, fn -> + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Invalid Post", + person_detail: {"John", ""} + }) + |> Ash.create!() + end + end +end From f05bc7a25ccbf2b0c75d3e66c97da7a854e5ccc5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 May 2025 10:33:47 -0400 Subject: [PATCH 1031/1215] chore: release version v2.5.20 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f5bc056..85c44395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.20](https://github.com/ash-project/ash_postgres/compare/v2.5.19...v2.5.20) (2025-05-20) + + + + +### Bug Fixes: + +* self-join if combination queries require more fields + +* enforce tenant name rules at creation (#550) + ## [v2.5.19](https://github.com/ash-project/ash_postgres/compare/v2.5.18...v2.5.19) (2025-05-06) diff --git a/mix.exs b/mix.exs index d0f3950c..0379c96d 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.19" + @version "2.5.20" def project do [ From 0910f17978811818c346b1188eba197822a0d265 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 May 2025 20:53:00 -0400 Subject: [PATCH 1032/1215] chore: fix test migrations --- mix.lock | 6 +++--- ...sources53.exs => 20250520130634_migrate_resources54.exs} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename priv/test_repo/migrations/{20250520130634_migrate_resources53.exs => 20250520130634_migrate_resources54.exs} (84%) diff --git a/mix.lock b/mix.lock index 2805978e..807d6691 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.10", "123d5f5840326e2aa60204c9dc74b43c1eb35258bffb630d3f4190ee2a123ef1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "79217d9017bdc3ce5e32f429b9f32b03ef77e07337ccf41f95ac79fe0b3851e0"}, + "ash": {:hex, :ash, "3.5.11", "6d20782aeeb99773bd6f4902883c4ff2ad18404b7ad84bfc464ee772133c7368", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f37122fe27f67a81624ba09de0fde56e84a3395ff41d457c075822427d741887"}, "ash_sql": {:hex, :ash_sql, "0.2.75", "94252b460db2e14b778fa542684811c48fa08b4b2a2916db6a05a33e4272c361", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "966226cd5b368258d05359c9a3f37cf86b412f5822a59c85ac27f047ca9e95cb"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.51", "21c3f9b425bd88572034644781057a793205b401ffd09e1c9498c528f14ef335", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9f1468d156145b463c6649c61e542befbf75f9d76055456d05c5f6adf7cfb272"}, + "igniter": {:hex, :igniter, "0.5.52", "18777a36918e3bb91c70f07b69f6a6589d8fa5547a7d210b228d410a2453923f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "8d75f0f2307e21b53ad96bd746f1806da91859ec0d4a68b203b763da4d5ae567"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.56", "9426f6b992b13cd2a83ba3567d7a9f8cf8e2f264d019983da12882b43351c9b6", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "ecdc66a91bfd9155047c4f36d594fdf7e72c5f721b451e5aa6967c5439fa2b65"}, + "spark": {:hex, :spark, "2.2.60", "1183d97cfd417d00902e3fafcaa154604f26e1d0ca558be0022006b7827a0ec8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "130c68bbe7d45dfb0c3e357d9778c487def0d15339b0b09b40e683c4f18aa2a5"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, diff --git a/priv/test_repo/migrations/20250520130634_migrate_resources53.exs b/priv/test_repo/migrations/20250520130634_migrate_resources54.exs similarity index 84% rename from priv/test_repo/migrations/20250520130634_migrate_resources53.exs rename to priv/test_repo/migrations/20250520130634_migrate_resources54.exs index cff5e211..9d8a51cb 100644 --- a/priv/test_repo/migrations/20250520130634_migrate_resources53.exs +++ b/priv/test_repo/migrations/20250520130634_migrate_resources54.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.TestRepo.Migrations.MigrateResources53 do +defmodule AshPostgres.TestRepo.Migrations.MigrateResources54 do @moduledoc """ Updates resources based on their most recent snapshots. From 6ddc29e66b03d4184e96ca042c486e00fc5b2929 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 May 2025 21:57:16 -0400 Subject: [PATCH 1033/1215] chore: add `usage-rules.md` --- usage-rules.md | 309 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 usage-rules.md diff --git a/usage-rules.md b/usage-rules.md new file mode 100644 index 00000000..5c8ee524 --- /dev/null +++ b/usage-rules.md @@ -0,0 +1,309 @@ +# Rules for working with AshPostgres + +## Understanding AshPostgres + +AshPostgres is the PostgreSQL data layer for Ash Framework. It's the most fully-featured Ash data layer and should be your default choice unless you have specific requirements for another data layer. Any PostgreSQL version higher than 13 is fully supported. + +## Basic Configuration + +To use AshPostgres, add the data layer to your resource: + +```elixir +defmodule MyApp.Tweet do + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + attributes do + integer_primary_key :id + attribute :text, :string + end + + relationships do + belongs_to :author, MyApp.User + end + + postgres do + table "tweets" + repo MyApp.Repo + end +end +``` + +## PostgreSQL Configuration + +### Table & Schema Configuration + +```elixir +postgres do + # Required: Define the table name for this resource + table "users" + + # Optional: Define the PostgreSQL schema + schema "public" + + # Required: Define the Ecto repo to use + repo MyApp.Repo + + # Optional: Control whether migrations are generated for this resource + migrate? true +end +``` + +## Foreign Key References + +Use the `references` section to configure foreign key behavior: + +```elixir +postgres do + table "comments" + repo MyApp.Repo + + references do + # Simple reference with defaults + reference :post + + # Fully configured reference + reference :user, + on_delete: :delete, # What happens when referenced row is deleted + on_update: :update, # What happens when referenced row is updated + name: "comments_to_users_fkey", # Custom constraint name + deferrable: true, # Make constraint deferrable + initially_deferred: false # Defer constraint check to end of transaction + end +end +``` + +### Foreign Key Actions + +For `on_delete` and `on_update` options: + +- `:nothing` or `:restrict` - Prevent the change to the referenced row +- `:delete` - Delete the row when the referenced row is deleted (for `on_delete` only) +- `:update` - Update the row according to changes in the referenced row (for `on_update` only) +- `:nilify` - Set all foreign key columns to NULL +- `{:nilify, columns}` - Set specific columns to NULL (Postgres 15.0+ only) + +> **Warning**: These operations happen directly at the database level. No resource logic, authorization rules, validations, or notifications are triggered. + +## Check Constraints + +Define database check constraints: + +```elixir +postgres do + check_constraints do + check_constraint :positive_amount, + check: "amount > 0", + name: "positive_amount_check", + message: "Amount must be positive" + + check_constraint :status_valid, + check: "status IN ('pending', 'active', 'completed')" + end +end +``` + +## Custom Indexes + +Define custom indexes beyond those automatically created for identities and relationships: + +```elixir +postgres do + custom_indexes do + index [:first_name, :last_name] + + index :email, + unique: true, + name: "users_email_index", + where: "email IS NOT NULL", + using: :gin + + index [:status, :created_at], + concurrently: true, + include: [:user_id] + end +end +``` + +## Custom SQL Statements + +Include custom SQL in migrations: + +```elixir +postgres do + custom_statements do + statement "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"" + + statement """ + CREATE TRIGGER update_updated_at + BEFORE UPDATE ON posts + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamp(); + """ + + statement "DROP INDEX IF EXISTS posts_title_index", + on_destroy: true # Only run when resource is destroyed/dropped + end +end +``` + +## Migrations and Codegen + +### Generating Migrations + +After creating or modifying Ash resources: + +1. Run `mix ash.codegen add_feature_name` to generate migrations +2. Review the generated migrations in `priv/repo/migrations` +3. Run `mix ash.migrate` to apply the migrations + +## Multitenancy + +AshPostgres supports schema-based multitenancy: + +```elixir +defmodule MyApp.Tenant do + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + # Resource definition... + + postgres do + table "tenants" + repo MyApp.Repo + + # Automatically create/manage tenant schemas + manage_tenant do + template ["tenant_", :id] + end + end +end +``` + +### Setting Up Multitenancy + +1. Configure your repo to support multitenancy: + +```elixir +defmodule MyApp.Repo do + use AshPostgres.Repo, otp_app: :my_app + + # Return all tenant schemas for migrations + def all_tenants do + import Ecto.Query, only: [from: 2] + all(from(t in "tenants", select: fragment("? || ?", "tenant_", t.id))) + end +end +``` + +2. Mark resources that should be multi-tenant: + +```elixir +defmodule MyApp.Post do + use Ash.Resource, + data_layer: AshPostgres.DataLayer + + multitenancy do + strategy :context + attribute :tenant + end + + # Resource definition... +end +``` + +3. When tenant migrations are generated, they'll be in `priv/repo/tenant_migrations` + +4. Run tenant migrations in addition to regular migrations: + +```bash +# Run regular migrations +mix ash.migrate + +# Run tenant migrations +mix ash_postgres.migrate --tenants +``` + +## Advanced Features + +### Manual Relationships + +For complex relationships that can't be expressed with standard relationship types: + +```elixir +defmodule MyApp.Post.Relationships.HighlyRatedComments do + use Ash.Resource.ManualRelationship + use AshPostgres.ManualRelationship + + def load(posts, _opts, context) do + post_ids = Enum.map(posts, & &1.id) + + {:ok, + MyApp.Comment + |> Ash.Query.filter(post_id in ^post_ids) + |> Ash.Query.filter(rating > 4) + |> MyApp.read!() + |> Enum.group_by(& &1.post_id)} + end + + def ash_postgres_join(query, _opts, current_binding, as_binding, :inner, destination_query) do + {:ok, + Ecto.Query.from(_ in query, + join: dest in ^destination_query, + as: ^as_binding, + on: dest.post_id == as(^current_binding).id, + on: dest.rating > 4 + )} + end + + # Other required callbacks... +end + +# In your resource: +relationships do + has_many :highly_rated_comments, MyApp.Comment do + manual MyApp.Post.Relationships.HighlyRatedComments + end +end +``` + +### Using Multiple Repos (Read Replicas) + +Configure different repos for reads vs mutations: + +```elixir +postgres do + repo fn resource, type -> + case type do + :read -> MyApp.ReadReplicaRepo + :mutate -> MyApp.WriteRepo + end + end +end +``` + +## Best Practices + +1. **Organize migrations**: Run `mix ash.codegen` after each meaningful set of resource changes with a descriptive name: + ```bash + mix ash.codegen --name add_user_roles + mix ash.codegen --name implement_post_tagging + ``` + +2. **Use check constraints for domain invariants**: Enforce data integrity at the database level: + ```elixir + check_constraints do + check_constraint :valid_status, check: "status IN ('pending', 'active', 'completed')" + check_constraint :positive_balance, check: "balance >= 0" + end + ``` + +3. **Use custom statements for schema-only changes**: If you need to add database objects not directly tied to resources: + ```elixir + custom_statements do + statement "CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"" + statement "CREATE INDEX users_search_idx ON users USING gin(search_vector)" + end + ``` + +Remember that using AshPostgres provides a full-featured PostgreSQL data layer for your Ash application, giving you both the structure and declarative approach of Ash along with the power and flexibility of PostgreSQL. From b2a6aa7492568c84ac953ad7eb09bc2e1da9cc07 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 20 May 2025 23:19:10 -0400 Subject: [PATCH 1034/1215] chore: include usage-rules.md in files --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 0379c96d..1e6be3c6 100644 --- a/mix.exs +++ b/mix.exs @@ -57,7 +57,7 @@ defmodule AshPostgres.MixProject do name: :ash_postgres, licenses: ["MIT"], files: ~w(lib .formatter.exs mix.exs README* LICENSE* - CHANGELOG* documentation), + CHANGELOG* documentation usage-rules.md), links: %{ Changelog: "https://hexdocs.pm/ash_postgres/changelog.html", GitHub: "https://github.com/ash-project/ash_postgres" From 9f9a209372a2d4fbcac38b1822ff7d57d7f692bc Mon Sep 17 00:00:00 2001 From: Marce Coll Date: Wed, 21 May 2025 14:53:32 +0200 Subject: [PATCH 1035/1215] chore: Create reproduction test of tuple Invalid filter value (#551) --- .../test_repo/posts/20250521105654.json | 600 ++++++++++++++++++ ...20250521105654_add_model_tuple_to_post.exs | 21 + test/support/domain.ex | 4 +- test/support/resources/post.ex | 28 + test/tuple_test.exs | 14 + 5 files changed, 666 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20250521105654.json create mode 100644 priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs diff --git a/priv/resource_snapshots/test_repo/posts/20250521105654.json b/priv/resource_snapshots/test_repo/posts/20250521105654.json new file mode 100644 index 00000000..ade3092c --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250521105654.json @@ -0,0 +1,600 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "metadata", + "type": "map" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "string_point", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "person_detail", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "model", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "points" + }, + "size": null, + "source": "db_point_id", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_string_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "string_points" + }, + "size": null, + "source": "db_string_point_id", + "type": "text" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "5925C6C41F5781689EA91BF2237C6F4E321FD0C969B2D7751265E448BF3236B7", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs b/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs new file mode 100644 index 00000000..bde744b0 --- /dev/null +++ b/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.AddModelTupleToPost 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(:posts) do + add(:model, :map, null: false) + end + end + + def down do + alter table(:posts) do + remove(:model) + end + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index eb1f047c..805a1dce 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -4,7 +4,9 @@ defmodule AshPostgres.Test.Domain do resources do resource(AshPostgres.Test.CoAuthorPost) - resource(AshPostgres.Test.Post) + resource(AshPostgres.Test.Post) do + define :review, action: :review + end resource(AshPostgres.Test.Comedian) resource(AshPostgres.Test.Comment) resource(AshPostgres.Test.CommentLink) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d2119d2a..ca293390 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -326,6 +326,9 @@ defmodule AshPostgres.Test.Post do require_atomic?(false) end + update :atomic_update do + end + update :update_if_author do require_atomic?(false) end @@ -413,6 +416,19 @@ defmodule AshPostgres.Test.Post do change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) end + update :review do + change(after_action(fn changeset, record, _context -> + new_model = {1.0, 2.0, 3.0} + + record + |> Ash.Changeset.for_update(:atomic_update) + |> Ash.Changeset.force_change_attribute(:model, new_model) + |> Ash.update() + end)) + + require_atomic?(false) + end + update :requires_initial_data do argument(:amount, :integer, default: 1) change(atomic_update(:score, expr((score || 0) + ^arg(:amount)))) @@ -508,6 +524,18 @@ defmodule AshPostgres.Test.Post do attribute(:uniq_on_upper, :string, public?: true) attribute(:uniq_if_contains_foo, :string, public?: true) + attribute :model, :tuple do + constraints [ + fields: [ + alpha: [type: :float, description: "The alpha field"], + beta: [type: :float, description: "The beta field"], + t: [type: :float, description: "The t field"] + ] + ] + allow_nil? false + default fn -> {3.0, 3.0, 1.0} end + end + attribute :list_containing_nils, {:array, :string} do public?(true) constraints(nil_items?: true) diff --git a/test/tuple_test.exs b/test/tuple_test.exs index 83b412f6..1233ac28 100644 --- a/test/tuple_test.exs +++ b/test/tuple_test.exs @@ -18,6 +18,20 @@ defmodule AshPostgres.Test.TupleTest do assert elem(post.person_detail, 1) == "Doe" end + test "tuple type with force_attribute_change can be updated" do + post = + Post + |> Ash.Changeset.for_create(:create, %{ + title: "Tuple Test", + score: 1 + }) + |> Ash.create!() + + post = AshPostgres.Test.Domain.review!(post) + + assert post.model == {1.0, 2.0, 3.0} + end + test "tuple type can be filtered by exact match" do # Create first post Post From 53514b1b272d6f47d2631ea0fc19aa7aa30680ce Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 May 2025 12:38:40 -0400 Subject: [PATCH 1036/1215] chore: fix tests --- test/support/resources/post.ex | 25 +++++++++++-------- .../resources/post_with_empty_update.ex | 13 ++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index ca293390..f996c324 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -417,14 +417,16 @@ defmodule AshPostgres.Test.Post do end update :review do - change(after_action(fn changeset, record, _context -> - new_model = {1.0, 2.0, 3.0} + change( + after_action(fn changeset, record, _context -> + new_model = {1.0, 2.0, 3.0} - record - |> Ash.Changeset.for_update(:atomic_update) - |> Ash.Changeset.force_change_attribute(:model, new_model) - |> Ash.update() - end)) + record + |> Ash.Changeset.for_update(:atomic_update) + |> Ash.Changeset.force_change_attribute(:model, new_model) + |> Ash.update() + end) + ) require_atomic?(false) end @@ -525,15 +527,16 @@ defmodule AshPostgres.Test.Post do attribute(:uniq_if_contains_foo, :string, public?: true) attribute :model, :tuple do - constraints [ + constraints( fields: [ alpha: [type: :float, description: "The alpha field"], beta: [type: :float, description: "The beta field"], t: [type: :float, description: "The t field"] ] - ] - allow_nil? false - default fn -> {3.0, 3.0, 1.0} end + ) + + allow_nil?(false) + default(fn -> {3.0, 3.0, 1.0} end) end attribute :list_containing_nils, {:array, :string} do diff --git a/test/support/resources/post_with_empty_update.ex b/test/support/resources/post_with_empty_update.ex index fcc1bace..da61629d 100644 --- a/test/support/resources/post_with_empty_update.ex +++ b/test/support/resources/post_with_empty_update.ex @@ -37,5 +37,18 @@ defmodule AshPostgres.Test.PostWithEmptyUpdate do public?(true) source(:title_column) end + + attribute :model, :tuple do + constraints( + fields: [ + alpha: [type: :float, description: "The alpha field"], + beta: [type: :float, description: "The beta field"], + t: [type: :float, description: "The t field"] + ] + ) + + allow_nil?(false) + default(fn -> {3.0, 3.0, 1.0} end) + end end end From 1ee37f9a1f760fff7d3bcad730217751141d56d0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 May 2025 17:57:17 -0400 Subject: [PATCH 1037/1215] improvement: update igniter, remove inflex --- lib/data_layer.ex | 2 +- lib/resource_generator/resource_generator.ex | 2 +- lib/resource_generator/spec.ex | 10 +++++----- mix.exs | 2 +- mix.lock | 3 +-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 5383ac26..ae642468 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3519,7 +3519,7 @@ defmodule AshPostgres.DataLayer do |> Module.split() |> List.last() |> Macro.underscore() - |> Inflex.pluralize() + |> Igniter.Inflex.pluralize() {options, _, _} = OptionParser.parse(argv, switches: [repo: :string]) diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index dbc8df14..99da8386 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -47,7 +47,7 @@ if Code.ensure_loaded?(Igniter) do |> Enum.map(fn %{table_name: table} = spec -> resource = table - |> Inflex.singularize() + |> Igniter.Inflex.singularize() |> Macro.camelize() |> then(&Module.concat([domain, &1])) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index a0f91cac..38eebac5 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -644,7 +644,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do [ %Relationship{ type: :belongs_to, - name: Inflex.singularize(references), + name: Igniter.Inflex.singularize(references), source: spec.resource, constraint_name: constraint_name, match_with: match_with, @@ -694,16 +694,16 @@ defmodule AshPostgres.ResourceGenerator.Spec do {name, type} = if has_unique_index? do - if Inflex.pluralize(table) == table do - {Inflex.singularize(table), :has_one} + if Igniter.Inflex.pluralize(table) == table do + {Igniter.Inflex.singularize(table), :has_one} else {table, :has_one} end else - if Inflex.pluralize(table) == table do + if Igniter.Inflex.pluralize(table) == table do {table, :has_many} else - {Inflex.pluralize(table), :has_many} + {Igniter.Inflex.pluralize(table), :has_many} end end diff --git a/mix.exs b/mix.exs index 1e6be3c6..fc97a6b3 100644 --- a/mix.exs +++ b/mix.exs @@ -168,7 +168,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.4 and >= 3.4.69")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, - {:igniter, "~> 0.5 and >= 0.5.16", optional: true}, + {:igniter, "~> 0.6", optional: true}, {:ecto_sql, "~> 3.12"}, {:ecto, "~> 3.12 and >= 3.12.1"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 807d6691..d5fc1b67 100644 --- a/mix.lock +++ b/mix.lock @@ -23,8 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.5.52", "18777a36918e3bb91c70f07b69f6a6589d8fa5547a7d210b228d410a2453923f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "8d75f0f2307e21b53ad96bd746f1806da91859ec0d4a68b203b763da4d5ae567"}, - "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "igniter": {:hex, :igniter, "0.6.0", "ad1e794286519bd52a0bc645419cdab1b8bf0352903199f534ea369ea5623dc4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "424df02a8acaeaec9cd01412916f60148d3ec710cd7317787c6656067f168f8b"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From 0720514ba4e66231f8231a1e7e6eaecc3d601eb5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 21 May 2025 17:57:31 -0400 Subject: [PATCH 1038/1215] chore: release version v2.5.21 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c44395..498a0f43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.21](https://github.com/ash-project/ash_postgres/compare/v2.5.20...v2.5.21) (2025-05-21) + + + + +### Improvements: + +* update igniter, remove inflex + ## [v2.5.20](https://github.com/ash-project/ash_postgres/compare/v2.5.19...v2.5.20) (2025-05-20) diff --git a/mix.exs b/mix.exs index fc97a6b3..4d9fc3a6 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.20" + @version "2.5.21" def project do [ From 6d693ed4e40e22963545ef36b468ae98ffef3a1d Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 21 May 2025 15:50:24 -0700 Subject: [PATCH 1039/1215] fix: Convert sensitive patterns from module constant to function for OTP/28 (#552) compatibility --- lib/resource_generator/sensitive_data.ex | 113 ++++++++++++----------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/lib/resource_generator/sensitive_data.ex b/lib/resource_generator/sensitive_data.ex index 77b0e45b..7b720aef 100644 --- a/lib/resource_generator/sensitive_data.ex +++ b/lib/resource_generator/sensitive_data.ex @@ -2,72 +2,75 @@ defmodule AshPostgres.ResourceGenerator.SensitiveData do @moduledoc false # I got this from ChatGPT, but this is a best effort transformation # anyway. - @sensitive_patterns [ - # Password-related - ~r/password/i, - ~r/passwd/i, - ~r/pass/i, - ~r/pwd/i, - ~r/hash(ed)?(_password)?/i, - # Authentication-related - ~r/auth(_key)?/i, - ~r/token/i, - ~r/secret(_key)?/i, - ~r/api_key/i, + def sensitive_patterns do + [ + # Password-related + ~r/password/i, + ~r/passwd/i, + ~r/pass/i, + ~r/pwd/i, + ~r/hash(ed)?(_password)?/i, - # Personal Information - ~r/ssn/i, - ~r/social(_security)?(_number)?/i, - ~r/(credit_?card|cc)(_number)?/i, - ~r/passport(_number)?/i, - ~r/driver_?licen(s|c)e(_number)?/i, - ~r/national_id/i, + # Authentication-related + ~r/auth(_key)?/i, + ~r/token/i, + ~r/secret(_key)?/i, + ~r/api_key/i, - # Financial Information - ~r/account(_number)?/i, - ~r/routing(_number)?/i, - ~r/iban/i, - ~r/swift(_code)?/i, - ~r/tax_id/i, + # Personal Information + ~r/ssn/i, + ~r/social(_security)?(_number)?/i, + ~r/(credit_?card|cc)(_number)?/i, + ~r/passport(_number)?/i, + ~r/driver_?licen(s|c)e(_number)?/i, + ~r/national_id/i, - # Contact Information - ~r/phone(_number)?/i, - ~r/email(_address)?/i, - ~r/address/i, + # Financial Information + ~r/account(_number)?/i, + ~r/routing(_number)?/i, + ~r/iban/i, + ~r/swift(_code)?/i, + ~r/tax_id/i, - # Health Information - ~r/medical(_record)?/i, - ~r/health(_data)?/i, - ~r/diagnosis/i, - ~r/treatment/i, + # Contact Information + ~r/phone(_number)?/i, + ~r/email(_address)?/i, + ~r/address/i, - # Biometric Data - ~r/fingerprint/i, - ~r/retina_scan/i, - ~r/face_id/i, - ~r/dna/i, + # Health Information + ~r/medical(_record)?/i, + ~r/health(_data)?/i, + ~r/diagnosis/i, + ~r/treatment/i, - # Encrypted or Encoded Data - ~r/encrypt(ed)?/i, - ~r/encoded/i, - ~r/cipher/i, + # Biometric Data + ~r/fingerprint/i, + ~r/retina_scan/i, + ~r/face_id/i, + ~r/dna/i, - # Other Potentially Sensitive Data - ~r/private(_key)?/i, - ~r/confidential/i, - ~r/restricted/i, - ~r/sensitive/i, + # Encrypted or Encoded Data + ~r/encrypt(ed)?/i, + ~r/encoded/i, + ~r/cipher/i, - # General patterns - ~r/.*_salt/i, - ~r/.*_secret/i, - ~r/.*_key/i, - ~r/.*_token/i - ] + # Other Potentially Sensitive Data + ~r/private(_key)?/i, + ~r/confidential/i, + ~r/restricted/i, + ~r/sensitive/i, + + # General patterns + ~r/.*_salt/i, + ~r/.*_secret/i, + ~r/.*_key/i, + ~r/.*_token/i + ] + end def sensitive?(column_name) do - Enum.any?(@sensitive_patterns, fn pattern -> + Enum.any?(sensitive_patterns(), fn pattern -> Regex.match?(pattern, column_name) end) end From 0325b028f93a31ad775caf0f26ad379975667d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Thu, 22 May 2025 12:44:28 +0200 Subject: [PATCH 1040/1215] Expand aggregate test covering rem expression (#541) --- test/aggregate_test.exs | 10 ++++++++++ test/support/resources/post.ex | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index bd34f579..e3ad5fa1 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -957,6 +957,16 @@ defmodule AshSql.AggregateTest do assert %{sum_of_popular_comment_rating_scores_2: 80} = values + + values = + post + |> Ash.load!([ + :sum_of_odd_comment_rating_scores + ]) + |> Map.take([:sum_of_odd_comment_rating_scores]) + + assert %{sum_of_popular_comment_rating_scores_2: 120} = + values end test "can't define multidimensional array aggregate types" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index f996c324..c66c8680 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1061,6 +1061,10 @@ defmodule AshPostgres.Test.Post do filter(expr(score > 5)) end + sum :sum_of_odd_comment_rating_scores, [:comments, :ratings], :score do + filter(expr(rem(score, 2) == 1)) + end + sum(:sum_of_popular_comment_rating_scores_2, [:comments, :popular_ratings], :score) sum :sum_of_comment_likes_called_match, :comments, :likes do From 593fa84cee9f7a36c070b364493d4fd3d4118790 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 22 May 2025 06:58:29 -0400 Subject: [PATCH 1041/1215] chore: format --- test/support/domain.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/support/domain.ex b/test/support/domain.ex index 805a1dce..25616374 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -4,9 +4,11 @@ defmodule AshPostgres.Test.Domain do resources do resource(AshPostgres.Test.CoAuthorPost) + resource(AshPostgres.Test.Post) do - define :review, action: :review + define(:review, action: :review) end + resource(AshPostgres.Test.Comedian) resource(AshPostgres.Test.Comment) resource(AshPostgres.Test.CommentLink) From 6b7336921731a8e0075a030f2c31a764bf3dc8bb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 22 May 2025 08:37:16 -0400 Subject: [PATCH 1042/1215] chore: release version v2.5.22 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 498a0f43..63141775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.5.22](https://github.com/ash-project/ash_postgres/compare/v2.5.21...v2.5.22) (2025-05-22) + + + + +### Bug Fixes: + +* Convert sensitive patterns from module constant to function for OTP/28 (#552) + ## [v2.5.21](https://github.com/ash-project/ash_postgres/compare/v2.5.20...v2.5.21) (2025-05-21) diff --git a/mix.exs b/mix.exs index 4d9fc3a6..31424e80 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.21" + @version "2.5.22" def project do [ From f573c42889ce9e190dd816b8020ccd282b513f3d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 22 May 2025 23:24:31 -0400 Subject: [PATCH 1043/1215] chore: fix aggregate test --- test/aggregate_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index e3ad5fa1..167a00c9 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -965,7 +965,7 @@ defmodule AshSql.AggregateTest do ]) |> Map.take([:sum_of_odd_comment_rating_scores]) - assert %{sum_of_popular_comment_rating_scores_2: 120} = + assert %{sum_of_odd_comment_rating_scores: 120} = values end From 38691cb67cd29da4e359142236de56fd4e7b2bc2 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 22 May 2025 23:27:08 -0400 Subject: [PATCH 1044/1215] chore: update deps --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index d5fc1b67..648043ae 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.11", "6d20782aeeb99773bd6f4902883c4ff2ad18404b7ad84bfc464ee772133c7368", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f37122fe27f67a81624ba09de0fde56e84a3395ff41d457c075822427d741887"}, - "ash_sql": {:hex, :ash_sql, "0.2.75", "94252b460db2e14b778fa542684811c48fa08b4b2a2916db6a05a33e4272c361", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "966226cd5b368258d05359c9a3f37cf86b412f5822a59c85ac27f047ca9e95cb"}, + "ash": {:hex, :ash, "3.5.12", "435a6916d47e4ed6eabce886de2443d17af9dd9ca9765a29c73d9e02507b86b3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.60 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "503492989c56e33c300d731b3717d1df9eeebba2b9018e5dd9f330db727edb57"}, + "ash_sql": {:hex, :ash_sql, "0.2.76", "4afac3284194f3d7820b7edc1263ac1eb232a25734406ad7b2284683b9384205", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f6bc02d8c4cba3f8f9a532e6ff73eaf8a4f7685257646a58fe7a320cfaf10c39"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -20,10 +20,10 @@ "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.7.2", "2d3c164a8bcaf13f129ab339e8e9f0a99c80ffa8f85dd0b344d7515275236dbc", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1dcd68b3f5bcd0999d69274cd21e74e652a90452e683b54d490fa5b26152945f"}, + "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.0", "ad1e794286519bd52a0bc645419cdab1b8bf0352903199f534ea369ea5623dc4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "424df02a8acaeaec9cd01412916f60148d3ec710cd7317787c6656067f168f8b"}, + "igniter": {:hex, :igniter, "0.6.1", "e683495de01cb3cb30943670fd93fc9f603093c380225a4a6dd1f468a393f891", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "81467df9d6e7210b262c41ccbbdb08c48491bd6fd3f2aee529a8ed730c6f84ba"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From c9f88460faf9341bf05fffa6f5d60006d46190ec Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 23 May 2025 17:26:48 -0400 Subject: [PATCH 1045/1215] improvement: support scale & precision in decimal types --- .../migration_generator.ex | 80 ++++- lib/migration_generator/operation.ex | 46 ++- lib/multitenancy.ex | 9 +- test/migration_generator_test.exs | 326 ++++++++++++++++++ 4 files changed, 446 insertions(+), 15 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index a0c04a02..231697ac 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -694,6 +694,8 @@ defmodule AshPostgres.MigrationGenerator do generated?: Enum.any?(attributes, & &1.generated?), references: merge_references(Enum.map(attributes, & &1.references), source, table), primary_key?: false, + scale: attributes |> Enum.map(& &1[:scale]) |> Enum.max(), + precision: attributes |> Enum.map(& &1[:precision]) |> Enum.max(), order: attributes |> Enum.map(& &1.order) |> Enum.min() } end) @@ -3012,19 +3014,25 @@ defmodule AshPostgres.MigrationGenerator do type end - {type, size} = + {type, size, precision, scale} = case type do {:varchar, size} -> - {:varchar, size} + {:varchar, size, nil, nil} {:binary, size} -> - {:binary, size} + {:binary, size, nil, nil} + + {:decimal, precision, scale} -> + {:decimal, nil, precision, scale} + + {:decimal, precision} -> + {:decimal, nil, precision, nil} {other, size} when is_atom(other) and is_integer(size) -> - {other, size} + {other, size, nil, nil} other -> - {other, nil} + {other, nil, nil, nil} end attribute @@ -3033,6 +3041,20 @@ defmodule AshPostgres.MigrationGenerator do |> Map.put(:type, type) |> Map.put(:source, attribute.source || attribute.name) |> Map.drop([:name, :constraints]) + |> then(fn map -> + if precision do + Map.put(map, :precision, precision) + else + map + end + end) + |> then(fn map -> + if scale do + Map.put(map, :scale, scale) + else + map + end + end) end) |> Enum.map(fn attribute -> references = find_reference(resource, table, attribute) @@ -3145,6 +3167,28 @@ defmodule AshPostgres.MigrationGenerator do end end + defp migration_type(Ash.Type.Decimal, constraints) do + precision = + case constraints[:precision] do + :arbitrary -> nil + nil -> nil + precision -> precision + end + + scale = + case constraints[:scale] do + :arbitrary -> nil + nil -> nil + scale -> scale + end + + cond do + precision && scale -> {:decimal, precision, scale} + precision -> {:decimal, precision} + true -> :decimal + end + end + defp migration_type(other, constraints) do type = Ash.Type.get_type(other) @@ -3451,19 +3495,25 @@ defmodule AshPostgres.MigrationGenerator do defp load_attribute(attribute, table) do type = load_type(attribute.type) - {type, size} = + {type, size, scale, precision} = case type do {:varchar, size} -> - {:varchar, size} + {:varchar, size, nil, nil} {:binary, size} -> - {:binary, size} + {:binary, size, nil, nil} {other, size} when is_atom(other) and is_integer(size) -> - {other, size} + {other, size, nil, nil} + + {:decimal, scale} -> + {:decimal, scale, nil, nil} + + {:decimal, scale, precision} -> + {:decimal, scale, precision, nil} other -> - {other, nil} + {other, nil, nil, nil} end attribute = @@ -3476,6 +3526,8 @@ defmodule AshPostgres.MigrationGenerator do attribute |> Map.put(:type, type) |> Map.put(:size, size) + |> Map.put(:precision, precision) + |> Map.put(:scale, scale) |> Map.put_new(:default, "nil") |> Map.update!(:default, &(&1 || "nil")) |> Map.update!(:references, fn @@ -3562,6 +3614,14 @@ defmodule AshPostgres.MigrationGenerator do {:binary, size} end + defp load_type(["decimal", scale]) do + {:decimal, scale} + end + + defp load_type(["decimal", scale, precision]) do + {:decimal, scale, precision} + end + defp load_type([string, size]) when is_binary(string) and is_integer(size) do {String.to_existing_atom(string), size} end diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 62b3b3a6..242a5db1 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -135,6 +135,12 @@ defmodule AshPostgres.MigrationGenerator.Operation do keys end end + + def maybe_add_precision(nil), do: nil + def maybe_add_precision(precision), do: "precision: #{precision}" + + def maybe_add_scale(nil), do: nil + def maybe_add_scale(scale), do: "scale: #{scale}" end defmodule CreateTable do @@ -179,7 +185,9 @@ defmodule AshPostgres.MigrationGenerator.Operation do option("prefix", destination_schema), on_delete(reference), on_update(reference), - size + size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]) ], ")", maybe_add_default(attribute.default), @@ -219,6 +227,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), on_delete(reference), on_update(reference) ], @@ -250,6 +260,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do maybe_add_default(attribute.default), maybe_add_primary_key(attribute.primary_key?), size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), maybe_add_null(attribute.allow_nil?) ] |> join() @@ -275,6 +287,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do maybe_add_default(attribute.default), maybe_add_primary_key(attribute.primary_key?), size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), maybe_add_null(attribute.allow_nil?) ] |> join() @@ -309,6 +323,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "type: #{inspect(reference_type(attribute, reference))}", "prefix: prefix()", size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), on_delete(reference), on_update(reference) ], @@ -355,6 +371,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "type: #{inspect(reference_type(attribute, reference))}", option("prefix", destination_schema), size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), on_delete(reference), on_update(reference) ], @@ -394,6 +412,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "type: #{inspect(reference_type(attribute, reference))}", option("prefix", destination_schema), size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), on_delete(reference), on_update(reference) ], @@ -437,6 +457,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do maybe_add_null(attribute.allow_nil?), maybe_add_default(attribute.default), size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), maybe_add_primary_key(attribute.primary_key?) ] |> join() @@ -538,7 +560,21 @@ defmodule AshPostgres.MigrationGenerator.Operation do ", null: #{attribute.allow_nil?}" end - "#{null}#{default}#{primary_key}" + precision = + if Map.get(attribute, :precision) != Map.get(old_attribute, :precision) do + if attribute.precision do + ", precision: #{attribute.precision}" + end + end + + scale = + if Map.get(attribute, :scale) != Map.get(old_attribute, :scale) do + if attribute.scale do + ", scale: #{attribute.scale}" + end + end + + "#{null}#{default}#{precision}#{scale}#{primary_key}" end def up(%{ @@ -587,6 +623,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), "prefix: prefix()", on_delete(reference), on_update(reference), @@ -625,6 +663,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), option("prefix", destination_schema), on_delete(reference), on_update(reference), @@ -699,6 +739,8 @@ defmodule AshPostgres.MigrationGenerator.Operation do "name: #{inspect(reference.name)}", "type: #{inspect(reference_type(attribute, reference))}", size, + maybe_add_precision(attribute[:precision]), + maybe_add_scale(attribute[:scale]), option("prefix", destination_schema), on_delete(reference), on_update(reference), diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 6927a960..96cddafc 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -3,7 +3,6 @@ defmodule AshPostgres.MultiTenancy do @dialyzer {:nowarn_function, load_migration!: 1} - @tenant_name_regex ~r/^[a-zA-Z0-9_-]+$/ def create_tenant!(tenant_name, repo) do validate_tenant_name!(tenant_name) Ecto.Adapters.SQL.query!(repo, "CREATE SCHEMA IF NOT EXISTS \"#{tenant_name}\"", []) @@ -87,8 +86,8 @@ defmodule AshPostgres.MultiTenancy do end defp validate_tenant_name!(tenant_name) do - if !Regex.match?(@tenant_name_regex, tenant_name) do - raise "Tenant name must match #{inspect(@tenant_name_regex)}, got: #{tenant_name}" + if !Regex.match?(tenant_name_regex(), tenant_name) do + raise "Tenant name must match #{inspect(tenant_name_regex())}, got: #{tenant_name}" end end @@ -100,4 +99,8 @@ defmodule AshPostgres.MultiTenancy do |> Path.join(repo_name) |> Path.join("tenant_migrations") end + + defp tenant_name_regex do + ~r/^[a-zA-Z0-9_-]+$/ + end end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 1ef76978..5b564b5f 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -2634,4 +2634,330 @@ defmodule AshPostgres.MigrationGeneratorTest do ~S[modify :post_id, references(:posts, column: :id, name: "comments_post_id_fkey", type: :uuid, prefix: "public")] end end + + describe "decimal precision and scale" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + end + + test "creates decimal columns with precision and scale" do + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + attribute(:price, :decimal, + constraints: [precision: 10, scale: 2], + public?: true, + allow_nil?: false + ) + + attribute(:weight, :decimal, + constraints: [precision: 8], + public?: true, + allow_nil?: false + ) + + attribute(:rating, :decimal, public?: true, allow_nil?: false) + end + end + + defdomain([Product]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + file_content = File.read!(file) + + # Check that precision and scale are included for the price field + assert file_content =~ ~S[add :price, :decimal, null: false, precision: 10, scale: 2] + + # Check that only precision is included for the weight field + assert file_content =~ ~S[add :weight, :decimal, null: false, precision: 8] + + # Check that no precision or scale is included for the rating field + assert file_content =~ ~S[add :rating, :decimal, null: false] + end + + test "alters decimal columns with precision and scale changes" do + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + attribute(:price, :decimal, constraints: [precision: 8, scale: 2], public?: true) + end + end + + defdomain([Product]) + + # Generate initial migration + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + # Now update the precision and scale + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + attribute(:price, :decimal, constraints: [precision: 12, scale: 4], public?: true) + end + end + + # Generate follow-up migration + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + migration_files = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert length(migration_files) == 2 + + # Check the second migration file + second_migration = File.read!(Enum.at(migration_files, 1)) + + # Should contain the alter statement with new precision and scale + assert second_migration =~ ~S[modify :price, :decimal, precision: 12, scale: 4] + end + + test "handles arbitrary precision and scale constraints" do + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + attribute(:price, :decimal, + constraints: [precision: :arbitrary, scale: :arbitrary], + public?: true, + allow_nil?: false + ) + end + end + + defdomain([Product]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + file_content = File.read!(file) + + # Check that no precision or scale is included when they are :arbitrary + assert file_content =~ ~S[add :price, :decimal, null: false] + refute file_content =~ ~S[precision:] + refute file_content =~ ~S[scale:] + end + + test "removes precision and scale when changing to arbitrary" do + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + attribute(:price, :decimal, + constraints: [precision: 10, scale: 2], + public?: true, + allow_nil?: false + ) + end + end + + defdomain([Product]) + + # Generate initial migration + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + # Now change to arbitrary precision and scale + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + attribute(:price, :decimal, + constraints: [precision: :arbitrary, scale: :arbitrary], + public?: true, + allow_nil?: false + ) + end + end + + # Generate follow-up migration + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + migration_files = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert length(migration_files) == 2 + + # Check the second migration file + second_migration = File.read!(Enum.at(migration_files, 1)) + + # Should contain the alter statement removing precision and scale + assert second_migration =~ ~S[modify :price, :decimal] + refute second_migration =~ ~S[precision:] + refute second_migration =~ ~S[scale:] + end + + test "works with decimal references that have precision and scale" do + defresource Category do + postgres do + table "categories" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + attribute(:id, :decimal, + constraints: [precision: 10, scale: 0], + primary_key?: true, + allow_nil?: false, + public?: true + ) + + attribute(:name, :string, public?: true) + end + end + + defresource Product do + postgres do + table "products" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + attributes do + uuid_primary_key(:id) + + attribute(:category_id, :decimal, + constraints: [precision: 10, scale: 0], + allow_nil?: false, + public?: true + ) + end + + relationships do + belongs_to(:category, Category) do + source_attribute(:category_id) + destination_attribute(:id) + public?(true) + end + end + end + + defdomain([Category, Product]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false + ) + + assert [file] = + Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) + |> Enum.reject(&String.contains?(&1, "extensions")) + + file_content = File.read!(file) + + # Check that both tables are created with proper decimal precision + assert file_content =~ + ~S[add :id, :decimal, null: false, precision: 10, scale: 0, primary_key: true] + + assert file_content =~ ~S[add :category_id, :decimal, null: false, precision: 10, scale: 0] + + assert file_content =~ + ~S[modify :category_id, references(:categories, column: :id, name: "products_category_id_fkey", type: :decimal, precision: 10, scale: 0] + end + end end From f0203458dd768cc2f7005f452e5a1ed03ea9bb20 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 25 May 2025 23:19:23 -0400 Subject: [PATCH 1046/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 648043ae..10820741 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.1", "e683495de01cb3cb30943670fd93fc9f603093c380225a4a6dd1f468a393f891", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "81467df9d6e7210b262c41ccbbdb08c48491bd6fd3f2aee529a8ed730c6f84ba"}, + "igniter": {:hex, :igniter, "0.6.2", "6263fa3d2b7698f8d9afc506e4e1bccef69f1b8a3288760fe749dc70a9f6b408", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "480c679066444459eecd371a41639cee365449e20cfb611457081394fd146b05"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -38,13 +38,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.15.2", "8c1b3fe0527b7a92b0b22c3f33f2e66858dd069bf1dd51d1031f63cd8cbd1fd5", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "091435a1fa0cab9bc2ed3934b203a0fd190f62e8b6aca63741f9242b8c7631ac"}, + "reactor": {:hex, :reactor, "0.15.3", "f1f05d5b0f229ad1a164b7a5543beee58c9975e7e146afc71821e5afc5c69a79", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "8a16a46163fcdeb1d1be06749f54bd71282126be27c9dc80010cf3b97fe7193c"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.60", "1183d97cfd417d00902e3fafcaa154604f26e1d0ca558be0022006b7827a0ec8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "130c68bbe7d45dfb0c3e357d9778c487def0d15339b0b09b40e683c4f18aa2a5"}, + "spark": {:hex, :spark, "2.2.61", "64745581832caf136cbd654c1ecef98d291f3d1b347a14411b7d0dc726e3822b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "cb10300e17f74b41848954d61630fb627f4e0d5acf9aacd7d9f312d5b119d50f"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From d18f6c60b27face846a9b75c603ec7e716515a31 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 May 2025 00:38:40 -0400 Subject: [PATCH 1047/1215] fix: properly encode decimal scale & preicison into snapshots --- .../migration_generator.ex | 22 ++++++++++++------- test/migration_generator_test.exs | 8 ++++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 231697ac..ea395da9 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3384,29 +3384,35 @@ defmodule AshPostgres.MigrationGenerator do references |> Map.update!(:on_delete, &(&1 && references_on_delete_to_binary(&1))) end) - |> Map.update!(:type, fn type -> sanitize_type(type, attribute[:size]) end) + |> Map.update!(:type, fn type -> + sanitize_type(type, attribute[:size], attribute[:precision], attribute[:scale]) + end) end defp references_on_delete_to_binary(value) when is_atom(value), do: value defp references_on_delete_to_binary({:nilify, columns}), do: [:nilify, columns] - defp sanitize_type({:array, type}, size) do - ["array", sanitize_type(type, size)] + defp sanitize_type({:array, type}, size, scale, precision) do + ["array", sanitize_type(type, size, scale, precision)] end - defp sanitize_type(:varchar, size) when not is_nil(size) do + defp sanitize_type(:varchar, size, _scale, _precision) when not is_nil(size) do ["varchar", size] end - defp sanitize_type(:binary, size) when not is_nil(size) do + defp sanitize_type(:binary, size, _scale, _precision) when not is_nil(size) do ["binary", size] end - defp sanitize_type(type, size) when is_atom(type) and is_integer(size) do - [sanitize_type(type, nil), size] + defp sanitize_type(:decimal, _size, scale, precision) do + ["decimal", scale, precision] |> Enum.reject(&is_nil/1) + end + + defp sanitize_type(type, size, precision, decimal) when is_atom(type) and is_integer(size) do + [sanitize_type(type, nil, precision, decimal), size] end - defp sanitize_type(type, _) do + defp sanitize_type(type, _, _, _) do type end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 5b564b5f..a1168ccb 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -2877,10 +2877,12 @@ defmodule AshPostgres.MigrationGeneratorTest do # Check the second migration file second_migration = File.read!(Enum.at(migration_files, 1)) + [up, _down] = String.split(second_migration, "def down") + # Should contain the alter statement removing precision and scale - assert second_migration =~ ~S[modify :price, :decimal] - refute second_migration =~ ~S[precision:] - refute second_migration =~ ~S[scale:] + assert up =~ ~S[modify :price, :decimal] + refute up =~ ~S[precision:] + refute up =~ ~S[scale:] end test "works with decimal references that have precision and scale" do From dac674b53b5ed7f195c3ffb07cff14b478697859 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 May 2025 21:42:51 -0400 Subject: [PATCH 1048/1215] feat: --dev flag for codegen (#555) --------- Co-authored-by: ken-kost --- config/config.exs | 10 +- .../development/migrations-and-tasks.md | 9 +- .../migration_generator.ex | 289 ++++++++++++++---- .../tasks/ash_postgres.generate_migrations.ex | 14 +- lib/mix/tasks/ash_postgres.migrate.ex | 11 +- lib/mix/tasks/ash_postgres.rollback.ex | 25 +- lib/multitenancy.ex | 14 +- mix.exs | 2 +- ...6214825_migrate_resources_extensions_1.exs | 172 +++++++++++ .../20250526214827_migrate_resources1.exs | 29 ++ .../dev_test_repo/extensions.json | 11 + .../multitenant_orgs/20250526214827.json | 70 +++++ test/dev_migrations_test.exs | 234 ++++++++++++++ test/migration_generator_test.exs | 189 ++++++++---- test/mix_squash_snapshots_test.exs | 9 +- test/support/dev_test_repo.ex | 39 +++ test/support/multitenancy/domain.ex | 1 + .../resources/dev_migrations_org.ex | 91 ++++++ test/test_helper.exs | 2 + usage-rules.md | 19 +- 20 files changed, 1091 insertions(+), 149 deletions(-) create mode 100644 priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs create mode 100644 priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs create mode 100644 priv/resource_snapshots/dev_test_repo/extensions.json create mode 100644 priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json create mode 100644 test/dev_migrations_test.exs create mode 100644 test/support/dev_test_repo.ex create mode 100644 test/support/multitenancy/resources/dev_migrations_org.ex diff --git a/config/config.exs b/config/config.exs index ee88e028..0041c0ac 100644 --- a/config/config.exs +++ b/config/config.exs @@ -37,6 +37,14 @@ if Mix.env() == :test do hostname: "localhost", pool: Ecto.Adapters.SQL.Sandbox + config :ash_postgres, AshPostgres.DevTestRepo, + username: "postgres", + password: "postgres", + database: "ash_postgres_dev_test", + hostname: "localhost", + migration_primary_key: [name: :id, type: :binary_id], + pool: Ecto.Adapters.SQL.Sandbox + # sobelow_skip ["Config.Secrets"] config :ash_postgres, AshPostgres.TestRepo, password: "postgres" @@ -54,7 +62,7 @@ if Mix.env() == :test do migration_primary_key: [name: :id, type: :binary_id] config :ash_postgres, - ecto_repos: [AshPostgres.TestRepo, AshPostgres.TestNoSandboxRepo], + ecto_repos: [AshPostgres.TestRepo, AshPostgres.DevTestRepo, AshPostgres.TestNoSandboxRepo], ash_domains: [ AshPostgres.Test.Domain, AshPostgres.MultitenancyTest.Domain, diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index a9d6b951..04fefc93 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -7,9 +7,16 @@ Ash comes with its own tasks, and AshPostgres exposes lower level tasks that you ## Basic Workflow - Make resource changes -- Run `mix ash.codegen --name add_a_combobulator` to generate migrations and resource snapshots +- Run `mix ash.codegen --dev` to generate a migration tagged as a `dev` migration, which will later be squashed and does not require a name. +- Run `mix ash.migrate` to run the migrations. +- Make some more resource changes. +- Once you're all done, run `mix ash.codegen add_a_combobulator`, using a good name for your changes to generate migrations and resource snapshots. This will **rollback** the dev migrations, and squash them into a the new named migration (or sometimes migrations). - Run `mix ash.migrate` to run those migrations +The `--dev` workflow enables you to avoid having to think of a name for migrations while developing, and also enables some +upcoming workflows that will detect when code generation needs to be run on page load and will show you a button to generate +dev migrations and run them. + For more information on generating migrations, run `mix help ash_postgres.generate_migrations` (the underlying task that is called by `mix ash.migrate`) > ### list_tenants/0 {: .info} diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index ea395da9..2479d067 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -19,7 +19,9 @@ defmodule AshPostgres.MigrationGenerator do format: true, dry_run: false, check: false, + dev: false, snapshots_only: false, + auto_name: false, dont_drop_columns: false def generate(domains, opts \\ []) do @@ -226,11 +228,13 @@ defmodule AshPostgres.MigrationGenerator do Mix.shell().info("No extensions to install") :ok else + dev = if opts.dev, do: "_dev" + {module, migration_name} = case to_install do [{ext_name, version, _up_fn, _down_fn}] -> {"install_#{ext_name}_v#{version}_#{timestamp(true)}", - "#{timestamp(true)}_install_#{ext_name}_v#{version}_extension"} + "#{timestamp(true)}_install_#{ext_name}_v#{version}_extension#{dev}"} ["ash_functions"] -> {"install_ash_functions_extension_#{AshPostgres.MigrationGenerator.AshFunctions.latest_version()}_#{timestamp(true)}", @@ -239,6 +243,8 @@ defmodule AshPostgres.MigrationGenerator do _multiple -> migration_path = migration_path(opts, repo, false) + require_name!(opts) + if opts.name do count = migration_path @@ -262,7 +268,7 @@ defmodule AshPostgres.MigrationGenerator do |> Kernel.+(1) {"#{opts.name}_extensions_#{count}", - "#{timestamp(true)}_#{opts.name}_extensions_#{count}"} + "#{timestamp(true)}_#{opts.name}_extensions_#{count}#{dev}"} else count = migration_path @@ -286,7 +292,7 @@ defmodule AshPostgres.MigrationGenerator do |> Kernel.+(1) {"migrate_resources_extensions_#{count}", - "#{timestamp(true)}_migrate_resources_extensions_#{count}"} + "#{timestamp(true)}_migrate_resources_extensions_#{count}#{dev}"} end end @@ -452,6 +458,23 @@ defmodule AshPostgres.MigrationGenerator do :ok operations -> + dev_migrations = get_dev_migrations(opts, tenant?, repo) + + if !opts.dev and dev_migrations != [] do + if opts.check do + Mix.shell().error(""" + Generated migrations are from dev mode. + + Generate migrations without `--dev` flag. + """) + + exit({:shutdown, 1}) + else + remove_dev_migrations(dev_migrations, tenant?, repo, opts) + remove_dev_snapshots(snapshots, opts) + end + end + if opts.check do Mix.shell().error(""" Migrations would have been generated, but the --check flag was provided. @@ -491,6 +514,159 @@ defmodule AshPostgres.MigrationGenerator do end) end + defp get_dev_migrations(opts, tenant?, repo) do + opts + |> migration_path(repo, tenant?) + |> File.ls() + |> case do + {:error, _error} -> [] + {:ok, migrations} -> Enum.filter(migrations, &String.contains?(&1, "_dev.exs")) + end + end + + if Mix.env() == :test do + defp with_repo_not_in_test(repo, fun) do + fun.(repo) + end + else + defp with_repo_not_in_test(repo, fun) do + Ecto.Migrator.with_repo(repo, fun) + end + end + + defp require_name!(opts) do + if !opts.name && !opts.dry_run && !opts.check && !opts.snapshots_only && !opts.dev && + !opts.auto_name do + raise """ + Name must be provided when generating migrations, unless `--dry-run` or `--check` or `--dev` is also provided. + + Please provide a name. for example: + + mix ash_postgres.generate_migrations ...args + """ + end + + :ok + end + + defp remove_dev_migrations(dev_migrations, tenant?, repo, opts) do + dev_migrations = + Enum.map(dev_migrations, fn migration -> + opts + |> migration_path(repo, tenant?) + |> Path.join(migration) + end) + + if tenant? do + with_repo_not_in_test(repo, fn repo -> + for prefix <- repo.all_tenants() do + {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], prefix) + + versions = repo.all(query, opts) + + dev_migrations + |> Enum.map(&extract_migration_info/1) + |> Enum.filter(& &1) + |> Enum.map(&load_migration!/1) + |> Enum.filter(fn {version, _} -> + version in versions + end) + |> Enum.each(fn {version, mod} -> + Ecto.Migration.Runner.run( + repo, + [], + version, + mod, + :forward, + :down, + :down, + all: true, + prefix: prefix + ) + + Ecto.Migration.SchemaMigration.down(repo, repo.config(), version, prefix: prefix) + end) + end + end) + else + with_repo_not_in_test(repo, fn repo -> + {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], nil) + + versions = repo.all(query, opts) + + dev_migrations + |> Enum.map(&extract_migration_info/1) + |> Enum.filter(& &1) + |> Enum.map(&load_migration!/1) + |> Enum.sort() + |> Enum.filter(fn {version, _} -> + version in versions + end) + |> Enum.each(fn {version, mod} -> + Ecto.Migration.Runner.run( + repo, + [], + version, + mod, + :forward, + :down, + :down, + all: true + ) + + Ecto.Migration.SchemaMigration.down(repo, repo.config(), version, []) + end) + end) + end + + Enum.each(dev_migrations, &File.rm!/1) + end + + defp extract_migration_info(file) do + base = Path.basename(file) + + case Integer.parse(Path.rootname(base)) do + {integer, "_" <> name} -> {integer, name, file} + _ -> nil + end + end + + defp load_migration!({version, _, file}) when is_binary(file) do + loaded_modules = file |> compile_file() |> Enum.map(&elem(&1, 0)) + + if mod = Enum.find(loaded_modules, &migration?/1) do + {version, mod} + else + raise Ecto.MigrationError, + "file #{Path.relative_to_cwd(file)} does not define an Ecto.Migration" + end + end + + defp compile_file(file) do + AshPostgres.MigrationCompileCache.start_link() + AshPostgres.MigrationCompileCache.compile_file(file) + end + + defp migration?(mod) do + function_exported?(mod, :__migration__, 0) + end + + def remove_dev_snapshots(snapshots, opts) do + Enum.each(snapshots, fn snapshot -> + folder = get_snapshot_folder(snapshot, opts) + snapshot_path = get_snapshot_path(snapshot, folder) + + snapshot_path + |> File.ls!() + |> Enum.filter(&String.contains?(&1, "_dev.json")) + |> Enum.each(fn snapshot_name -> + snapshot_path + |> Path.join(snapshot_name) + |> File.rm!() + end) + end) + end + defp split_into_migrations(operations) do operations |> Enum.split_with(fn @@ -932,6 +1108,8 @@ defmodule AshPostgres.MigrationGenerator do defp write_migration!({up, down}, repo, opts, tenant?, run_without_transaction?) do migration_path = migration_path(opts, repo, tenant?) + require_name!(opts) + {migration_name, last_part} = if opts.name do {"#{timestamp(true)}_#{opts.name}", "#{opts.name}"} @@ -962,7 +1140,7 @@ defmodule AshPostgres.MigrationGenerator do migration_file = migration_path - |> Path.join(migration_name <> ".exs") + |> Path.join(migration_name <> "#{if opts.dev, do: "_dev"}.exs") module_name = if tenant? do @@ -1056,20 +1234,25 @@ defmodule AshPostgres.MigrationGenerator do |> Path.join(repo_name) end + dev = if opts.dev, do: "_dev" + snapshot_file = if snapshot.schema do - Path.join(snapshot_folder, "#{snapshot.schema}.#{snapshot.table}/#{timestamp()}.json") + Path.join( + snapshot_folder, + "#{snapshot.schema}.#{snapshot.table}/#{timestamp()}#{dev}.json" + ) else - Path.join(snapshot_folder, "#{snapshot.table}/#{timestamp()}.json") + Path.join(snapshot_folder, "#{snapshot.table}/#{timestamp()}#{dev}.json") end File.mkdir_p(Path.dirname(snapshot_file)) create_file(snapshot_file, snapshot_binary, force: true) - old_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}.json") + old_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}#{dev}.json") if File.exists?(old_snapshot_folder) do - new_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}/initial.json") + new_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}/initial#{dev}.json") File.rename(old_snapshot_folder, new_snapshot_folder) end end) @@ -2653,43 +2836,22 @@ defmodule AshPostgres.MigrationGenerator do end def get_existing_snapshot(snapshot, opts) do - repo_name = snapshot.repo |> Module.split() |> List.last() |> Macro.underscore() - - folder = - if snapshot.multitenancy.strategy == :context do - opts - |> snapshot_path(snapshot.repo) - |> Path.join(repo_name) - |> Path.join("tenants") - else - opts - |> snapshot_path(snapshot.repo) - |> Path.join(repo_name) - end - - snapshot_folder = - if snapshot.schema do - schema_dir = Path.join(folder, "#{snapshot.schema}.#{snapshot.table}") + folder = get_snapshot_folder(snapshot, opts) + snapshot_path = get_snapshot_path(snapshot, folder) - if File.dir?(schema_dir) do - schema_dir - else - Path.join(folder, snapshot.table) - end - else - Path.join(folder, snapshot.table) - end - - if File.exists?(snapshot_folder) do - snapshot_folder + if File.exists?(snapshot_path) do + snapshot_path |> File.ls!() - |> Enum.filter(&String.match?(&1, ~r/^\d{14}\.json$/)) + |> Enum.filter( + &(String.match?(&1, ~r/^\d{14}\.json$/) or + (opts.dev and String.match?(&1, ~r/^\d{14}\_dev.json$/))) + ) |> case do [] -> get_old_snapshot(folder, snapshot) snapshot_files -> - snapshot_folder + snapshot_path |> Path.join(Enum.max(snapshot_files)) |> File.read!() |> load_snapshot() @@ -2699,6 +2861,33 @@ defmodule AshPostgres.MigrationGenerator do end end + defp get_snapshot_folder(snapshot, opts) do + if snapshot.multitenancy.strategy == :context do + opts + |> snapshot_path(snapshot.repo) + |> Path.join(repo_name(snapshot.repo)) + |> Path.join("tenants") + else + opts + |> snapshot_path(snapshot.repo) + |> Path.join(repo_name(snapshot.repo)) + end + end + + defp get_snapshot_path(snapshot, folder) do + if snapshot.schema do + schema_dir = Path.join(folder, "#{snapshot.schema}.#{snapshot.table}") + + if File.dir?(schema_dir) do + schema_dir + else + Path.join(folder, snapshot.table) + end + else + Path.join(folder, snapshot.table) + end + end + defp get_old_snapshot(folder, snapshot) do schema_file = if snapshot.schema do @@ -3182,11 +3371,7 @@ defmodule AshPostgres.MigrationGenerator do scale -> scale end - cond do - precision && scale -> {:decimal, precision, scale} - precision -> {:decimal, precision} - true -> :decimal - end + {:decimal, precision, scale} end defp migration_type(other, constraints) do @@ -3405,7 +3590,7 @@ defmodule AshPostgres.MigrationGenerator do end defp sanitize_type(:decimal, _size, scale, precision) do - ["decimal", scale, precision] |> Enum.reject(&is_nil/1) + ["decimal", precision, scale] end defp sanitize_type(type, size, precision, decimal) when is_atom(type) and is_integer(size) do @@ -3512,11 +3697,11 @@ defmodule AshPostgres.MigrationGenerator do {other, size} when is_atom(other) and is_integer(size) -> {other, size, nil, nil} - {:decimal, scale} -> - {:decimal, scale, nil, nil} + {:decimal, precision} -> + {:decimal, nil, nil, precision} - {:decimal, scale, precision} -> - {:decimal, scale, precision, nil} + {:decimal, precision, scale} -> + {:decimal, nil, precision, scale} other -> {other, nil, nil, nil} @@ -3620,12 +3805,12 @@ defmodule AshPostgres.MigrationGenerator do {:binary, size} end - defp load_type(["decimal", scale]) do - {:decimal, scale} + defp load_type(["decimal", precision]) do + {:decimal, precision} end - defp load_type(["decimal", scale, precision]) do - {:decimal, scale, precision} + defp load_type(["decimal", precision, scale]) do + {:decimal, precision, scale} end defp load_type([string, size]) when is_binary(string) and is_integer(size) do diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 065942c8..8431572c 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -21,6 +21,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do * `no-format` - files that are created will not be formatted with the code formatter * `dry-run` - no files are created, instead the new migration is printed * `check` - no files are created, returns an exit(1) code if the current snapshots and resources don't fit + * `dev` - dev files are created * `snapshots-only` - no migrations are generated, only snapshots are stored * `concurrent-indexes` - new identities will be run concurrently and in a separate migration (like concurrent custom indexes) @@ -93,10 +94,12 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do tenant_migration_path: :string, quiet: :boolean, snapshots_only: :boolean, + auto_name: :boolean, name: :string, no_format: :boolean, dry_run: :boolean, check: :boolean, + dev: :boolean, dont_drop_columns: :boolean, concurrent_indexes: :boolean ] @@ -110,17 +113,6 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do |> Keyword.delete(:no_format) |> Keyword.put_new(:name, name) - if !opts[:name] && !opts[:dry_run] && !opts[:check] && !opts[:snapshots_only] do - IO.warn(""" - Name must be provided when generating migrations, unless `--dry-run` or `--check` is also provided. - Using an autogenerated name will be deprecated in a future release. - - Please provide a name. for example: - - mix ash_postgres.generate_migrations #{Enum.join(args, " ")} - """) - end - AshPostgres.MigrationGenerator.generate(domains, opts) end end diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 36b9187d..3299fc5b 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -119,6 +119,8 @@ defmodule Mix.Tasks.AshPostgres.Migrate do |> AshPostgres.Mix.Helpers.delete_flag("--tenants") |> AshPostgres.Mix.Helpers.delete_arg("--only-tenants") |> AshPostgres.Mix.Helpers.delete_arg("--except-tenants") + |> AshPostgres.Mix.Helpers.delete_arg("--repo") + |> AshPostgres.Mix.Helpers.delete_arg("-r") Mix.Task.reenable("ecto.migrate") @@ -128,9 +130,16 @@ defmodule Mix.Tasks.AshPostgres.Migrate do for tenant <- tenants(repo, opts) do rest_opts = AshPostgres.Mix.Helpers.delete_arg(rest_opts, "--prefix") + repo = + if is_atom(repo) do + inspect(repo) + else + repo + end + Mix.Task.run( "ecto.migrate", - ["-r", to_string(repo)] ++ + ["-r", repo] ++ rest_opts ++ ["--prefix", tenant, "--migrations-path", tenant_migrations_path(opts, repo)] ) diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index b9a8354a..add81d59 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -61,6 +61,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do log_migrator_sql: :boolean, only_tenants: :string, except_tenants: :string, + already_started: :boolean, repo: :string ], aliases: [n: :step, v: :to, r: :repo] @@ -76,25 +77,23 @@ defmodule Mix.Tasks.AshPostgres.Rollback do |> AshPostgres.Mix.Helpers.delete_flag("--only-tenants") |> AshPostgres.Mix.Helpers.delete_flag("--except-tenants") |> AshPostgres.Mix.Helpers.delete_arg("-r") + |> AshPostgres.Mix.Helpers.delete_arg("--already-started") Mix.Task.reenable("ecto.rollback") if opts[:tenants] do for repo <- repos do - Ecto.Migrator.with_repo(repo, fn repo -> - for tenant <- tenants(repo, opts) do - rest_opts = AshPostgres.Mix.Helpers.delete_arg(rest_opts, "--prefix") + for tenant <- tenants(repo, opts) do + Mix.Task.reenable("ecto.rollback") + rest_opts = AshPostgres.Mix.Helpers.delete_arg(rest_opts, "--prefix") - Mix.Task.run( - "ecto.rollback", - ["-r", to_string(repo)] ++ - rest_opts ++ - ["--prefix", tenant, "--migrations-path", tenant_migrations_path(opts, repo)] - ) - - Mix.Task.reenable("ecto.rollback") - end - end) + Mix.Task.run( + "ecto.rollback", + ["-r", to_string(repo)] ++ + rest_opts ++ + ["--prefix", tenant, "--migrations-path", tenant_migrations_path(opts, repo)] + ) + end end else for repo <- repos do diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 96cddafc..eac6d15a 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -3,6 +3,7 @@ defmodule AshPostgres.MultiTenancy do @dialyzer {:nowarn_function, load_migration!: 1} + # sobelow_skip ["SQL.Query"] def create_tenant!(tenant_name, repo) do validate_tenant_name!(tenant_name) Ecto.Adapters.SQL.query!(repo, "CREATE SCHEMA IF NOT EXISTS \"#{tenant_name}\"", []) @@ -10,7 +11,7 @@ defmodule AshPostgres.MultiTenancy do migrate_tenant(tenant_name, repo) end - def migrate_tenant(tenant_name, repo, migrations_path \\ nil) do + def migrate_tenant(tenant_name, repo, migrations_path \\ nil, after_file \\ nil) do tenant_migrations_path = migrations_path || repo.config()[:tenant_migrations_path] || default_tenant_migration_path(repo) @@ -24,6 +25,17 @@ defmodule AshPostgres.MultiTenancy do [tenant_migrations_path, "**", "*.exs"] |> Path.join() |> Path.wildcard() + |> then(fn files -> + if after_file do + files + |> Enum.drop_while(fn file -> + file != after_file + end) + |> Enum.drop(1) + else + files + end + end) |> Enum.map(&extract_migration_info/1) |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) diff --git a/mix.exs b/mix.exs index 31424e80..fb84295b 100644 --- a/mix.exs +++ b/mix.exs @@ -240,7 +240,7 @@ defmodule AshPostgres.MixProject do format: "format --migrate", "spark.formatter": "spark.formatter --extensions AshPostgres.DataLayer", "spark.cheat_sheets": "spark.cheat_sheets --extensions AshPostgres.DataLayer", - "test.generate_migrations": "ash_postgres.generate_migrations", + "test.generate_migrations": "ash_postgres.generate_migrations --auto-name", "test.check_migrations": "ash_postgres.generate_migrations --check", "test.migrate_tenants": "ash_postgres.migrate --tenants", "test.migrate": "ash_postgres.migrate", diff --git a/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs b/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs new file mode 100644 index 00000000..389b0b27 --- /dev/null +++ b/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs @@ -0,0 +1,172 @@ +defmodule AshPostgres.DevTestRepo.Migrations.MigrateResourcesExtensions1 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE(NULLIF($1, FALSE), $2) $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_or(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) + AS $$ SELECT COALESCE($1, $2) $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left BOOLEAN, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS TRUE THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_elixir_and(left ANYCOMPATIBLE, in right ANYCOMPATIBLE, out f1 ANYCOMPATIBLE) AS $$ + SELECT CASE + WHEN $1 IS NOT NULL THEN $2 + ELSE $1 + END $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_trim_whitespace(arr text[]) + RETURNS text[] AS $$ + DECLARE + start_index INT = 1; + end_index INT = array_length(arr, 1); + BEGIN + WHILE start_index <= end_index AND arr[start_index] = '' LOOP + start_index := start_index + 1; + END LOOP; + + WHILE end_index >= start_index AND arr[end_index] = '' LOOP + end_index := end_index - 1; + END LOOP; + + IF start_index > end_index THEN + RETURN ARRAY[]::text[]; + ELSE + RETURN arr[start_index : end_index]; + END IF; + END; $$ + LANGUAGE plpgsql + SET search_path = '' + IMMUTABLE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql + STABLE + SET search_path = ''; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error(json_data jsonb, type_signal ANYCOMPATIBLE) + RETURNS ANYCOMPATIBLE AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql + STABLE + SET search_path = ''; + """) + + execute(""" + CREATE OR REPLACE FUNCTION uuid_generate_v7() + RETURNS UUID + AS $$ + DECLARE + timestamp TIMESTAMPTZ; + microseconds INT; + BEGIN + timestamp = clock_timestamp(); + microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT; + + RETURN encode( + set_byte( + set_byte( + overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6 + ), + 6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int + ), + 7, microseconds::bit(8)::int + ), + 'hex')::UUID; + END + $$ + LANGUAGE PLPGSQL + SET search_path = '' + VOLATILE; + """) + + execute(""" + CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid) + RETURNS TIMESTAMP WITHOUT TIME ZONE + AS $$ + SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000); + $$ + LANGUAGE SQL + SET search_path = '' + IMMUTABLE PARALLEL SAFE STRICT; + """) + + execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"") + execute("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\"") + execute("CREATE EXTENSION IF NOT EXISTS \"citext\"") + + execute(""" + CREATE OR REPLACE FUNCTION ash_demo_functions() + RETURNS boolean AS $$ SELECT TRUE $$ + LANGUAGE SQL + IMMUTABLE; + """) + + execute("CREATE EXTENSION IF NOT EXISTS \"ltree\"") + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute( + "DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid), ash_raise_error(jsonb), ash_raise_error(jsonb, ANYCOMPATIBLE), ash_elixir_and(BOOLEAN, ANYCOMPATIBLE), ash_elixir_and(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(ANYCOMPATIBLE, ANYCOMPATIBLE), ash_elixir_or(BOOLEAN, ANYCOMPATIBLE), ash_trim_whitespace(text[])" + ) + + # execute("DROP EXTENSION IF EXISTS \"uuid-ossp\"") + # execute("DROP EXTENSION IF EXISTS \"pg_trgm\"") + # execute("DROP EXTENSION IF EXISTS \"citext\"") + execute(""" + DROP FUNCTION IF EXISTS ash_demo_functions() + """) + + # execute("DROP EXTENSION IF EXISTS \"ltree\"") + end +end diff --git a/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs b/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs new file mode 100644 index 00000000..3a56462c --- /dev/null +++ b/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs @@ -0,0 +1,29 @@ +defmodule AshPostgres.DevTestRepo.Migrations.MigrateResources1 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 + create table(:multitenant_orgs, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text) + add(:owner_id, :uuid) + end + + create( + unique_index(:multitenant_orgs, [:id, :name], name: "multitenant_orgs_unique_by_name_index") + ) + end + + def down do + drop_if_exists( + unique_index(:multitenant_orgs, [:id, :name], name: "multitenant_orgs_unique_by_name_index") + ) + + drop(table(:multitenant_orgs)) + end +end diff --git a/priv/resource_snapshots/dev_test_repo/extensions.json b/priv/resource_snapshots/dev_test_repo/extensions.json new file mode 100644 index 00000000..d1c5a122 --- /dev/null +++ b/priv/resource_snapshots/dev_test_repo/extensions.json @@ -0,0 +1,11 @@ +{ + "ash_functions_version": 5, + "installed": [ + "ash-functions", + "uuid-ossp", + "pg_trgm", + "citext", + "demo-functions_v1", + "ltree" + ] +} \ No newline at end of file diff --git a/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json b/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json new file mode 100644 index 00000000..072cfe57 --- /dev/null +++ b/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json @@ -0,0 +1,70 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "owner_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "979D7BEB5939EAD2706978B7914C561B5F6854EBBCFD1F2748AB4C98668304EB", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "multitenant_orgs_unique_by_name_index", + "keys": [ + { + "type": "atom", + "value": "name" + } + ], + "name": "unique_by_name", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": "id", + "global": true, + "strategy": "attribute" + }, + "repo": "Elixir.AshPostgres.DevTestRepo", + "schema": null, + "table": "multitenant_orgs" +} \ No newline at end of file diff --git a/test/dev_migrations_test.exs b/test/dev_migrations_test.exs new file mode 100644 index 00000000..475aa909 --- /dev/null +++ b/test/dev_migrations_test.exs @@ -0,0 +1,234 @@ +defmodule AshPostgres.DevMigrationsTest do + use AshPostgres.RepoCase, async: false + @moduletag :migration + + import ExUnit.CaptureLog + require Logger + + alias Ecto.Adapters.SQL.Sandbox + + setup do + current_shell = Mix.shell() + + :ok = Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(current_shell) + end) + + Sandbox.checkout(AshPostgres.DevTestRepo) + end + + defmacrop defresource(mod, do: body) do + quote do + Code.compiler_options(ignore_module_conflict: true) + + defmodule unquote(mod) do + use Ash.Resource, + domain: nil, + data_layer: AshPostgres.DataLayer + + unquote(body) + end + + Code.compiler_options(ignore_module_conflict: false) + end + end + + defmacrop defposts(do: body) do + quote do + defresource Post do + postgres do + table "posts" + repo(AshPostgres.DevTestRepo) + + custom_indexes do + # need one without any opts + index(["id"]) + index(["id"], unique: true, name: "test_unique_index") + end + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + unquote(body) + end + end + end + + defmacrop defdomain(resources) do + quote do + Code.compiler_options(ignore_module_conflict: true) + + defmodule Domain do + use Ash.Domain + + resources do + for resource <- unquote(resources) do + resource(resource) + end + end + end + + Code.compiler_options(ignore_module_conflict: false) + end + end + + setup do + resource_dev_path = "priv/resource_snapshots/dev_test_repo" + + initial_resource_files = + if File.exists?(resource_dev_path), do: File.ls!(resource_dev_path), else: [] + + migrations_dev_path = "priv/dev_test_repo/migrations" + + initial_migration_files = + if File.exists?(migrations_dev_path), do: File.ls!(migrations_dev_path), else: [] + + tenant_migrations_dev_path = "priv/dev_test_repo/tenant_migrations" + + initial_tenant_migration_files = + if File.exists?(tenant_migrations_dev_path), + do: File.ls!(tenant_migrations_dev_path), + else: [] + + on_exit(fn -> + if File.exists?(resource_dev_path) do + current_resource_files = File.ls!(resource_dev_path) + new_resource_files = current_resource_files -- initial_resource_files + Enum.each(new_resource_files, &File.rm_rf!(Path.join(resource_dev_path, &1))) + end + + if File.exists?(migrations_dev_path) do + current_migration_files = File.ls!(migrations_dev_path) + new_migration_files = current_migration_files -- initial_migration_files + Enum.each(new_migration_files, &File.rm!(Path.join(migrations_dev_path, &1))) + end + + if File.exists?(tenant_migrations_dev_path) do + current_tenant_migration_files = File.ls!(tenant_migrations_dev_path) + + new_tenant_migration_files = + current_tenant_migration_files -- initial_tenant_migration_files + + Enum.each( + new_tenant_migration_files, + &File.rm!(Path.join(tenant_migrations_dev_path, &1)) + ) + end + + AshPostgres.DevTestRepo.query!("DROP TABLE IF EXISTS posts") + end) + end + + describe "--dev option" do + test "rolls back dev migrations before deleting" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "priv/resource_snapshots", + migration_path: "priv/dev_test_repo/migrations", + dev: true, + auto_name: true + ) + + assert [_extensions, migration, _migration] = + Path.wildcard("priv/dev_test_repo/migrations/**/*_migrate_resources*.exs") + + assert capture_log(fn -> migrate(migration) end) =~ "create table posts" + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "priv/resource_snapshots", + migration_path: "priv/dev_test_repo/migrations", + auto_name: true + ) + + assert capture_log(fn -> migrate(migration) end) =~ "create table posts" + end + end + + describe "--dev option tenant" do + test "rolls back dev migrations before deleting" do + defposts do + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true, primary_key?: true, allow_nil?: false) + end + + multitenancy do + strategy(:context) + end + end + + defdomain([Post]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "priv/resource_snapshots", + migration_path: "priv/dev_test_repo/migrations", + tenant_migration_path: "priv/dev_test_repo/tenant_migrations", + dev: true, + auto_name: true + ) + + org = + AshPostgres.MultitenancyTest.DevMigrationsOrg + |> Ash.Changeset.for_create(:create, %{name: "test1"}, authorize?: false) + |> Ash.create!() + + assert [_] = + Enum.sort( + Path.wildcard("priv/dev_test_repo/migrations/**/*_migrate_resources*.exs") + ) + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert [_tenant_migration] = + Enum.sort( + Path.wildcard("priv/dev_test_repo/tenant_migrations/**/*_migrate_resources*.exs") + ) + |> Enum.reject(&String.contains?(&1, "extensions")) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "priv/resource_snapshots", + migration_path: "priv/dev_test_repo/migrations", + tenant_migration_path: "priv/dev_test_repo/tenant_migrations", + auto_name: true + ) + + assert [_tenant_migration] = + Enum.sort( + Path.wildcard("priv/dev_test_repo/tenant_migrations/**/*_migrate_resources*.exs") + ) + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert capture_log(fn -> tenant_migrate() end) =~ "create table org_#{org.id}.posts" + end + end + + defp migrate(after_file) do + AshPostgres.MultiTenancy.migrate_tenant( + nil, + AshPostgres.DevTestRepo, + "priv/dev_test_repo/migrations", + after_file + ) + end + + defp tenant_migrate do + for tenant <- AshPostgres.DevTestRepo.all_tenants() do + AshPostgres.MultiTenancy.migrate_tenant( + tenant, + AshPostgres.DevTestRepo, + "priv/dev_test_repo/tenant_migrations" + ) + end + end +end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index a1168ccb..6a4d0ef5 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -147,7 +147,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -247,7 +248,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -326,7 +328,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) end @@ -371,7 +374,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) end @@ -414,7 +418,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) end @@ -440,7 +445,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -493,7 +499,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -518,7 +525,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -552,7 +560,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, _file2, file3] = @@ -605,7 +614,8 @@ defmodule AshPostgres.MigrationGeneratorTest do migration_path: "test_migration_path", tenant_migration_path: "test_tenant_migration_path", quiet: false, - format: false + format: false, + auto_name: true ) defposts do @@ -632,7 +642,8 @@ defmodule AshPostgres.MigrationGeneratorTest do migration_path: "test_migration_path", tenant_migration_path: "test_tenant_migration_path", quiet: false, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -675,7 +686,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -701,7 +713,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -731,7 +744,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -774,7 +788,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -805,7 +820,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1] = @@ -838,7 +854,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -864,7 +881,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -897,7 +915,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -930,7 +949,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -966,7 +986,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -993,7 +1014,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1019,7 +1041,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1048,7 +1071,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1083,7 +1107,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1156,7 +1181,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1189,7 +1215,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1232,7 +1259,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file1, file2] = @@ -1277,7 +1305,8 @@ defmodule AshPostgres.MigrationGeneratorTest do migration_path: "test_migration_path", quiet: true, concurrent_indexes: true, - format: false + format: false, + auto_name: true ) assert [_file1, _file2, file3] = @@ -1316,7 +1345,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -1356,7 +1386,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -1394,7 +1425,8 @@ defmodule AshPostgres.MigrationGeneratorTest do AshPostgres.MigrationGenerator.generate(domain, snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", - check: true + check: true, + auto_name: true ) ) == {:shutdown, 1} @@ -1437,7 +1469,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1474,7 +1507,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1520,7 +1554,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1566,7 +1601,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1617,7 +1653,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) defposts Post2 do @@ -1646,7 +1683,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -1727,7 +1765,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1781,7 +1820,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -1863,7 +1903,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1918,7 +1959,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -1957,7 +1999,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) defposts Post2 do @@ -1983,7 +2026,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -2089,7 +2133,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -2169,7 +2214,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -2209,7 +2255,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -2242,7 +2289,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -2288,7 +2336,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -2323,7 +2372,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) defposts do @@ -2337,7 +2387,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert file = @@ -2414,7 +2465,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) [domain: Domain] @@ -2471,7 +2523,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file1] = @@ -2527,7 +2580,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) end) @@ -2576,7 +2630,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -2614,7 +2669,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [_file1, file2] = @@ -2679,7 +2735,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -2722,7 +2779,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) # Now update the precision and scale @@ -2747,7 +2805,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) migration_files = @@ -2791,7 +2850,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = @@ -2835,7 +2895,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) # Now change to arbitrary precision and scale @@ -2865,7 +2926,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) migration_files = @@ -2943,7 +3005,8 @@ defmodule AshPostgres.MigrationGeneratorTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) assert [file] = diff --git a/test/mix_squash_snapshots_test.exs b/test/mix_squash_snapshots_test.exs index f2827228..dcdce819 100644 --- a/test/mix_squash_snapshots_test.exs +++ b/test/mix_squash_snapshots_test.exs @@ -94,7 +94,8 @@ defmodule AshPostgres.MixSquashSnapshotsTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) defposts do @@ -113,7 +114,8 @@ defmodule AshPostgres.MixSquashSnapshotsTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok @@ -179,7 +181,8 @@ defmodule AshPostgres.MixSquashSnapshotsTest do snapshot_path: "test_snapshots_path", migration_path: "test_migration_path", quiet: true, - format: false + format: false, + auto_name: true ) :ok diff --git a/test/support/dev_test_repo.ex b/test/support/dev_test_repo.ex new file mode 100644 index 00000000..2b1fe929 --- /dev/null +++ b/test/support/dev_test_repo.ex @@ -0,0 +1,39 @@ +defmodule AshPostgres.DevTestRepo do + @moduledoc false + use AshPostgres.Repo, + otp_app: :ash_postgres + + def on_transaction_begin(data) do + send(self(), data) + end + + def prefer_transaction?, do: false + + def prefer_transaction_for_atomic_updates?, do: false + + def installed_extensions do + ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- + Application.get_env(:ash_postgres, :no_extensions, []) + end + + def min_pg_version do + case System.get_env("PG_VERSION") do + nil -> + %Version{major: 16, minor: 0, patch: 0} + + version -> + case Integer.parse(version) do + {major, ""} -> %Version{major: major, minor: 0, patch: 0} + _ -> Version.parse!(version) + end + end + end + + def all_tenants do + Code.ensure_compiled(AshPostgres.MultitenancyTest.Org) + + AshPostgres.MultitenancyTest.DevMigrationsOrg + |> Ash.read!() + |> Enum.map(&"org_#{&1.id}") + end +end diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 9ad6f881..52ffbc63 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -4,6 +4,7 @@ defmodule AshPostgres.MultitenancyTest.Domain do resources do resource(AshPostgres.MultitenancyTest.Org) + resource(AshPostgres.MultitenancyTest.DevMigrationsOrg) resource(AshPostgres.MultitenancyTest.NamedOrg) resource(AshPostgres.MultitenancyTest.User) resource(AshPostgres.MultitenancyTest.Post) diff --git a/test/support/multitenancy/resources/dev_migrations_org.ex b/test/support/multitenancy/resources/dev_migrations_org.ex new file mode 100644 index 00000000..6ebd3394 --- /dev/null +++ b/test/support/multitenancy/resources/dev_migrations_org.ex @@ -0,0 +1,91 @@ +defmodule AshPostgres.MultitenancyTest.DevMigrationsOrg do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + defimpl Ash.ToTenant do + def to_tenant(%{id: id}, resource) do + if Ash.Resource.Info.data_layer(resource) == AshPostgres.DataLayer && + Ash.Resource.Info.multitenancy_strategy(resource) == :context do + "org_#{id}" + else + id + end + end + end + + policies do + policy action(:has_policies) do + authorize_if(relates_to_actor_via(:owner)) + end + + # policy always() do + # authorize_if(always()) + # end + end + + identities do + identity(:unique_by_name, [:name]) + end + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string, public?: true) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + + read(:has_policies) + end + + postgres do + table "multitenant_orgs" + repo(AshPostgres.DevTestRepo) + + manage_tenant do + template(["org_", :id]) + end + end + + multitenancy do + strategy(:attribute) + attribute(:id) + global?(true) + parse_attribute({__MODULE__, :tenant, []}) + end + + aggregates do + count(:total_users_posts, [:users, :posts]) + count(:total_posts, :posts) + end + + relationships do + belongs_to :owner, AshPostgres.MultitenancyTest.User do + attribute_public?(false) + public?(false) + end + + has_many(:posts, AshPostgres.MultitenancyTest.Post, + destination_attribute: :org_id, + public?: true + ) + + has_many(:users, AshPostgres.MultitenancyTest.User, + destination_attribute: :org_id, + public?: true + ) + end + + def tenant("org_" <> tenant) do + tenant + end + + def tenant(tenant) do + tenant + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 056ebefd..6745f6eb 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -20,6 +20,7 @@ exclude_tags = ExUnit.configure(stacktrace_depth: 100, exclude: exclude_tags) AshPostgres.TestRepo.start_link() +AshPostgres.DevTestRepo.start_link() AshPostgres.TestNoSandboxRepo.start_link() format_sql_query = @@ -49,4 +50,5 @@ format_sql_query = end Ecto.DevLogger.install(AshPostgres.TestRepo, before_inline_callback: format_sql_query) +Ecto.DevLogger.install(AshPostgres.DevTestRepo, before_inline_callback: format_sql_query) Ecto.DevLogger.install(AshPostgres.TestNoSandboxRepo, before_inline_callback: format_sql_query) diff --git a/usage-rules.md b/usage-rules.md index 5c8ee524..632fc52d 100644 --- a/usage-rules.md +++ b/usage-rules.md @@ -149,14 +149,29 @@ end ## Migrations and Codegen -### Generating Migrations +### Development Migration Workflow (Recommended) -After creating or modifying Ash resources: +For development iterations, use the dev workflow to avoid naming migrations prematurely: + +1. Make resource changes +2. Run `mix ash.codegen --dev` to generate and run dev migrations +3. Review the migrations and run `mix ash.migrate` to run them +4. Continue making changes and running `mix ash.codegen --dev` as needed +5. When your feature is complete, run `mix ash.codegen add_feature_name` to generate final named migrations (this will rollback dev migrations and squash them) +3. Review the migrations and run `mix ash.migrate` to run them + +### Traditional Migration Generation + +For single-step changes or when you know the final feature name: 1. Run `mix ash.codegen add_feature_name` to generate migrations 2. Review the generated migrations in `priv/repo/migrations` 3. Run `mix ash.migrate` to apply the migrations +> **Tip**: The dev workflow (`--dev` flag) is preferred during development as it allows you to iterate without thinking of migration names and provides better development ergonomics. + +> **Warning**: Always review migrations before applying them to ensure they are correct and safe. + ## Multitenancy AshPostgres supports schema-based multitenancy: From 831940d3368e2db536a43928fad142c54b0da761 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 May 2025 22:09:49 -0400 Subject: [PATCH 1049/1215] chore: temporarily override ash dep --- mix.exs | 3 ++- mix.lock | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index fb84295b..a0399b50 100644 --- a/mix.exs +++ b/mix.exs @@ -166,7 +166,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + # {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + {:ash, github: "ash-project/ash", override: true}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.6", optional: true}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index 10820741..624ec23b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.12", "435a6916d47e4ed6eabce886de2443d17af9dd9ca9765a29c73d9e02507b86b3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.60 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "503492989c56e33c300d731b3717d1df9eeebba2b9018e5dd9f330db727edb57"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "3eee4fa4fd836e8dad7bc8fb09df602221867565", []}, "ash_sql": {:hex, :ash_sql, "0.2.76", "4afac3284194f3d7820b7edc1263ac1eb232a25734406ad7b2284683b9384205", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f6bc02d8c4cba3f8f9a532e6ff73eaf8a4f7685257646a58fe7a320cfaf10c39"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -44,7 +44,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.61", "64745581832caf136cbd654c1ecef98d291f3d1b347a14411b7d0dc726e3822b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "cb10300e17f74b41848954d61630fb627f4e0d5acf9aacd7d9f312d5b119d50f"}, + "spark": {:hex, :spark, "2.2.62", "610502559834465edce437de712bf7e6d59713ad48050789dbef69a798e71a3c", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "726df72e1b9c17401584b4657e75e08a27a1cf6a6effa2486bf1c074da6176a7"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From d226fcd79d09d6aec926019ebc227d7d5eb9322f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 26 May 2025 22:25:16 -0400 Subject: [PATCH 1050/1215] ci: temporarily only have one postgres-version --- .github/workflows/elixir.yml | 2 +- mix.exs | 3 +-- mix.lock | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 084df209..cb875c93 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - postgres-version: ["14", "15", "16"] + postgres-version: ["16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true diff --git a/mix.exs b/mix.exs index a0399b50..fb84295b 100644 --- a/mix.exs +++ b/mix.exs @@ -166,8 +166,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - {:ash, github: "ash-project/ash", override: true}, + {:ash, ash_version("~> 3.4 and >= 3.4.69")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.6", optional: true}, {:ecto_sql, "~> 3.12"}, diff --git a/mix.lock b/mix.lock index 624ec23b..1f94b151 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "3eee4fa4fd836e8dad7bc8fb09df602221867565", []}, + "ash": {:hex, :ash, "3.5.12", "435a6916d47e4ed6eabce886de2443d17af9dd9ca9765a29c73d9e02507b86b3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.60 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "503492989c56e33c300d731b3717d1df9eeebba2b9018e5dd9f330db727edb57"}, "ash_sql": {:hex, :ash_sql, "0.2.76", "4afac3284194f3d7820b7edc1263ac1eb232a25734406ad7b2284683b9384205", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f6bc02d8c4cba3f8f9a532e6ff73eaf8a4f7685257646a58fe7a320cfaf10c39"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From cb7a216e3d59df03430cd5699b737965cc29a687 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 27 May 2025 22:05:47 -0400 Subject: [PATCH 1051/1215] chore: try to migrate in transaction to see if that helps --- .../migration_generator.ex | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2479d067..7516aa96 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -562,12 +562,46 @@ defmodule AshPostgres.MigrationGenerator do for prefix <- repo.all_tenants() do {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], prefix) + repo.transaction(fn -> + versions = repo.all(query, Keyword.put(opts, :timeout, :infinity)) + + dev_migrations + |> Enum.map(&extract_migration_info/1) + |> Enum.filter(& &1) + |> Enum.map(&load_migration!/1) + |> Enum.filter(fn {version, _} -> + version in versions + end) + |> Enum.each(fn {version, mod} -> + Ecto.Migration.Runner.run( + repo, + [], + version, + mod, + :forward, + :down, + :down, + all: true, + prefix: prefix + ) + + Ecto.Migration.SchemaMigration.down(repo, repo.config(), version, prefix: prefix) + end) + end) + end + end) + else + with_repo_not_in_test(repo, fn repo -> + {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], nil) + + repo.transaction(fn -> versions = repo.all(query, opts) dev_migrations |> Enum.map(&extract_migration_info/1) |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) + |> Enum.sort() |> Enum.filter(fn {version, _} -> version in versions end) @@ -580,41 +614,11 @@ defmodule AshPostgres.MigrationGenerator do :forward, :down, :down, - all: true, - prefix: prefix + all: true ) - Ecto.Migration.SchemaMigration.down(repo, repo.config(), version, prefix: prefix) + Ecto.Migration.SchemaMigration.down(repo, repo.config(), version, []) end) - end - end) - else - with_repo_not_in_test(repo, fn repo -> - {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], nil) - - versions = repo.all(query, opts) - - dev_migrations - |> Enum.map(&extract_migration_info/1) - |> Enum.filter(& &1) - |> Enum.map(&load_migration!/1) - |> Enum.sort() - |> Enum.filter(fn {version, _} -> - version in versions - end) - |> Enum.each(fn {version, mod} -> - Ecto.Migration.Runner.run( - repo, - [], - version, - mod, - :forward, - :down, - :down, - all: true - ) - - Ecto.Migration.SchemaMigration.down(repo, repo.config(), version, []) end) end) end From c52ec455889dbd9a8fb2223274306677eb852a11 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 27 May 2025 22:10:55 -0400 Subject: [PATCH 1052/1215] CI: add back in older pg versions --- .github/workflows/elixir.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index cb875c93..084df209 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - postgres-version: ["16"] + postgres-version: ["14", "15", "16"] uses: ash-project/ash/.github/workflows/ash-ci.yml@main with: postgres: true From 69b06eeba6754773b3a32eadc0cf526d5bea6142 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 27 May 2025 22:20:45 -0400 Subject: [PATCH 1053/1215] CI: add temporary logging for CI strangeness --- lib/multitenancy.ex | 4 ++++ test/dev_migrations_test.exs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index eac6d15a..3469b0f4 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -21,6 +21,7 @@ defmodule AshPostgres.MultiTenancy do repo.config(), prefix: tenant_name ) + |> tap(fn v -> Logger.warning(inspect(v, label: "ensure migraitons result")) end) [tenant_migrations_path, "**", "*.exs"] |> Path.join() @@ -39,6 +40,7 @@ defmodule AshPostgres.MultiTenancy do |> Enum.map(&extract_migration_info/1) |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) + |> tap(fn v -> Logger.warning(inspect(v, label: "migrations")) end) |> Enum.each(fn {version, mod} -> Ecto.Migration.Runner.run( repo, @@ -51,8 +53,10 @@ defmodule AshPostgres.MultiTenancy do all: true, prefix: tenant_name ) + |> tap(fn v -> Logger.warning(inspect(v, label: "run result")) end) Ecto.Migration.SchemaMigration.up(repo, repo.config(), version, prefix: tenant_name) + |> tap(fn v -> Logger.warning(inspect(v, label: "schema run result")) end) end) end diff --git a/test/dev_migrations_test.exs b/test/dev_migrations_test.exs index 475aa909..0f7b93c0 100644 --- a/test/dev_migrations_test.exs +++ b/test/dev_migrations_test.exs @@ -220,6 +220,7 @@ defmodule AshPostgres.DevMigrationsTest do "priv/dev_test_repo/migrations", after_file ) + |> tap(fn v -> Logger.warning(inspect(v, label: "result")) end) end defp tenant_migrate do @@ -229,6 +230,7 @@ defmodule AshPostgres.DevMigrationsTest do AshPostgres.DevTestRepo, "priv/dev_test_repo/tenant_migrations" ) + |> tap(fn v -> Logger.warning(inspect(v, label: "result")) end) end end end From bb9024f5616e47f44a48ffbbfd6c19064565bb24 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 27 May 2025 22:21:46 -0400 Subject: [PATCH 1054/1215] chore: add require logger --- lib/multitenancy.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 3469b0f4..a8601209 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -2,6 +2,7 @@ defmodule AshPostgres.MultiTenancy do @moduledoc false @dialyzer {:nowarn_function, load_migration!: 1} + require Logger # sobelow_skip ["SQL.Query"] def create_tenant!(tenant_name, repo) do From 08b2e44391fe8b42fa063d0b49824daae21c1c0e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 27 May 2025 22:39:22 -0400 Subject: [PATCH 1055/1215] chore: try sorting migration files --- lib/multitenancy.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index a8601209..30abecbd 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -27,6 +27,7 @@ defmodule AshPostgres.MultiTenancy do [tenant_migrations_path, "**", "*.exs"] |> Path.join() |> Path.wildcard() + |> Enum.sort() |> then(fn files -> if after_file do files From 1ce6568271cae5ede04c1448e2ebed489ec108ab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 27 May 2025 23:07:46 -0400 Subject: [PATCH 1056/1215] chore: add missing sort --- lib/migration_generator/migration_generator.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7516aa96..57e0787e 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -569,6 +569,7 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.map(&extract_migration_info/1) |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) + |> Enum.sort() |> Enum.filter(fn {version, _} -> version in versions end) From bdd71d742772544c6250b0fea6e0e5e5f04541f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 10:29:24 -0400 Subject: [PATCH 1057/1215] chore(deps): bump igniter in the production-dependencies group (#556) Bumps the production-dependencies group with 1 update: [igniter](https://github.com/ash-project/igniter). Updates `igniter` from 0.6.2 to 0.6.3 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.6.2...v0.6.3) --- updated-dependencies: - dependency-name: igniter dependency-version: 0.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 1f94b151..3f532193 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.2", "6263fa3d2b7698f8d9afc506e4e1bccef69f1b8a3288760fe749dc70a9f6b408", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "480c679066444459eecd371a41639cee365449e20cfb611457081394fd146b05"}, + "igniter": {:hex, :igniter, "0.6.3", "8bfaf5955ce83301da0f0a53455f73a0bc4dc5aacd6c311363089850a5dc2dd7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "95d34d8280dea992e05dcbf9865414d69e421a76d743661eaf1d1337ea54fa80"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From e415d9e2fc5557dab9d4ffd1042b984d7b9a5922 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 10:29:35 -0400 Subject: [PATCH 1058/1215] chore(deps-dev): bump ex_doc in the dev-dependencies group (#557) Bumps the dev-dependencies group with 1 update: [ex_doc](https://github.com/elixir-lang/ex_doc). Updates `ex_doc` from 0.38.1 to 0.38.2 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.38.1...v0.38.2) --- updated-dependencies: - dependency-name: ex_doc dependency-version: 0.38.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 3f532193..4076ca19 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.38.1", "bae0a0bd5b5925b1caef4987e3470902d072d03347114ffe03a55dbe206dd4c2", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "754636236d191b895e1e4de2ebb504c057fe1995fdfdd92e9d75c4b05633008b"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, From 3f837d14e80ece91aca6fd8ec7fc7c7be713d39f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 29 May 2025 22:15:50 -0400 Subject: [PATCH 1059/1215] improvement: assume not renaming when generating dev migrations --- lib/migration_generator/migration_generator.ex | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 57e0787e..2d5dfca1 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2960,10 +2960,14 @@ defmodule AshPostgres.MigrationGenerator do end defp renaming?(table, removing, opts) do - if opts.no_shell? do - raise "Unimplemented: cannot determine: Are you renaming #{table}.#{removing.source}? without shell input" + if opts.dev do + if opts.no_shell? do + raise "Unimplemented: cannot determine: Are you renaming #{table}.#{removing.source}? without shell input" + else + yes?(opts, "Are you renaming #{table}.#{removing.source}?") + end else - yes?(opts, "Are you renaming #{table}.#{removing.source}?") + false end end From 755c5564abd94f0b017e854cd0aaa2bb29e14c15 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 May 2025 00:25:24 -0400 Subject: [PATCH 1060/1215] improvement: use new `PendingCodegen` error --- .tool-versions | 2 +- lib/data_layer.ex | 1 + .../migration_generator.ex | 204 ++++++++---------- mix.exs | 31 +-- mix.lock | 2 +- test/migration_generator_test.exs | 18 +- 6 files changed, 118 insertions(+), 140 deletions(-) diff --git a/.tool-versions b/.tool-versions index cab727e0..005db769 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 27.0.1 -elixir 1.18.0-otp-27 +elixir 1.18.4-otp-27 diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ae642468..40227e7f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -568,6 +568,7 @@ defmodule AshPostgres.DataLayer do end def codegen(args) do + Mix.Task.reenable("ash_postgres.generate_migrations") Mix.Task.run("ash_postgres.generate_migrations", args) end diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 2d5dfca1..de411a97 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3,8 +3,6 @@ defmodule AshPostgres.MigrationGenerator do require Logger - import Mix.Generator - alias AshPostgres.MigrationGenerator.{Operation, Phase} defstruct snapshot_path: nil, @@ -52,12 +50,46 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.concat(find_repos()) |> Enum.uniq() - Mix.shell().info("\nExtension Migrations: ") - create_extension_migrations(repos, opts) - Mix.shell().info("\nGenerating Tenant Migrations: ") - create_migrations(tenant_snapshots, opts, true, snapshots) - Mix.shell().info("\nGenerating Migrations:") - create_migrations(snapshots, opts, false) + extension_migration_files = + create_extension_migrations(repos, opts) + + tenant_migration_files = + create_migrations(tenant_snapshots, opts, true, snapshots) + + migration_files = + create_migrations(snapshots, opts, false) + + case extension_migration_files ++ tenant_migration_files ++ migration_files do + [] -> + if !opts.check || opts.dry_run do + Mix.shell().info( + "No changes detected, so no migrations or snapshots have been created." + ) + end + + :ok + + files -> + cond do + opts.check -> + raise Ash.Error.Framework.PendingCodegen, + diff: files + + opts.dry_run -> + Mix.shell().info( + files + |> Enum.sort_by(&elem(&1, 0)) + |> Enum.map_join("\n\n", fn {file, contents} -> + "#{file}\n#{contents}" + end) + ) + + true -> + Enum.each(files, fn {file, contents} -> + Mix.Generator.create_file(file, contents, force?: true) + end) + end + end end defp find_repos do @@ -225,8 +257,7 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.map(fn {_name, extension} -> extension end) if Enum.empty?(to_install) do - Mix.shell().info("No extensions to install") - :ok + [] else dev = if opts.dev, do: "_dev" @@ -378,40 +409,13 @@ defmodule AshPostgres.MigrationGenerator do contents = format(migration_file, contents, opts) - if opts.dry_run do - Mix.shell().info(snapshot_contents) - Mix.shell().info(contents) - - if opts.check do - Mix.shell().error(""" - Migrations would have been generated, but the --check flag was provided. - - To see what migration would have been generated, run with the `--dry-run` - option instead. To generate those migrations, run without either flag. - """) - - exit({:shutdown, 1}) - end - else - if opts.check do - Mix.shell().error(""" - Migrations would have been generated, but the --check flag was provided. - - To see what migration would have been generated, run with the `--dry-run` - option instead. To generate those migrations, run without either flag. - """) - - exit({:shutdown, 1}) - end - - create_file(snapshot_file, snapshot_contents, force: true) - - if !opts.snapshots_only do - create_file(migration_file, contents) - end - end + [ + {snapshot_file, snapshot_contents}, + {migration_file, contents} + ] end end + |> List.flatten() end defp set_ash_functions(snapshot, installed_extensions) do @@ -429,7 +433,7 @@ defmodule AshPostgres.MigrationGenerator do defp create_migrations(snapshots, opts, tenant?, non_tenant_snapshots \\ []) do snapshots |> Enum.group_by(& &1.repo) - |> Enum.each(fn {repo, snapshots} -> + |> Enum.flat_map(fn {repo, snapshots} -> deduped = deduplicate_snapshots(snapshots, opts, non_tenant_snapshots) snapshots_with_operations = @@ -444,18 +448,7 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.uniq() |> case do [] -> - tenant_str = - if tenant? do - "tenant " - else - "" - end - - Mix.shell().info( - "No #{tenant_str}changes detected, so no migrations or snapshots have been created." - ) - - :ok + [] operations -> dev_migrations = get_dev_migrations(opts, tenant?, repo) @@ -475,21 +468,12 @@ defmodule AshPostgres.MigrationGenerator do end end - if opts.check do - Mix.shell().error(""" - Migrations would have been generated, but the --check flag was provided. - - To see what migration would have been generated, run with the `--dry-run` - option instead. To generate those migrations, run without either flag. - """) - - exit({:shutdown, 1}) - end - - if !opts.snapshots_only do + if opts.snapshots_only do + [] + else operations |> split_into_migrations() - |> Enum.each(fn operations -> + |> Enum.map(fn operations -> run_without_transaction? = Enum.any?(operations, fn %Operation.AddCustomIndex{index: %{concurrently: true}} -> @@ -505,11 +489,10 @@ defmodule AshPostgres.MigrationGenerator do operations |> organize_operations |> build_up_and_down() - |> write_migration!(repo, opts, tenant?, run_without_transaction?) + |> migration(repo, opts, tenant?, run_without_transaction?) end) end - - create_new_snapshot(snapshots, repo_name(repo), opts, tenant?) + |> Enum.concat(create_new_snapshot(snapshots, repo_name(repo), opts, tenant?)) end end) end @@ -1110,7 +1093,7 @@ defmodule AshPostgres.MigrationGenerator do repo |> Module.split() |> List.last() |> Macro.underscore() end - defp write_migration!({up, down}, repo, opts, tenant?, run_without_transaction?) do + defp migration({up, down}, repo, opts, tenant?, run_without_transaction?) do migration_path = migration_path(opts, repo, tenant?) require_name!(opts) @@ -1185,13 +1168,7 @@ defmodule AshPostgres.MigrationGenerator do """ try do - contents = format(migration_file, contents, opts) - - if opts.dry_run do - Mix.shell().info(contents) - else - create_file(migration_file, contents) - end + {migration_file, format(migration_file, contents, opts)} rescue exception -> reraise( @@ -1223,45 +1200,44 @@ defmodule AshPostgres.MigrationGenerator do end defp create_new_snapshot(snapshots, repo_name, opts, tenant?) do - if !opts.dry_run do - Enum.each(snapshots, fn snapshot -> - snapshot_binary = snapshot_to_binary(snapshot) + Enum.map(snapshots, fn snapshot -> + snapshot_binary = snapshot_to_binary(snapshot) - snapshot_folder = - if tenant? do - opts - |> snapshot_path(snapshot.repo) - |> Path.join(repo_name) - |> Path.join("tenants") - else - opts - |> snapshot_path(snapshot.repo) - |> Path.join(repo_name) - end + snapshot_folder = + if tenant? do + opts + |> snapshot_path(snapshot.repo) + |> Path.join(repo_name) + |> Path.join("tenants") + else + opts + |> snapshot_path(snapshot.repo) + |> Path.join(repo_name) + end - dev = if opts.dev, do: "_dev" + dev = if opts.dev, do: "_dev" - snapshot_file = - if snapshot.schema do - Path.join( - snapshot_folder, - "#{snapshot.schema}.#{snapshot.table}/#{timestamp()}#{dev}.json" - ) - else - Path.join(snapshot_folder, "#{snapshot.table}/#{timestamp()}#{dev}.json") - end + snapshot_file = + if snapshot.schema do + Path.join( + snapshot_folder, + "#{snapshot.schema}.#{snapshot.table}/#{timestamp()}#{dev}.json" + ) + else + Path.join(snapshot_folder, "#{snapshot.table}/#{timestamp()}#{dev}.json") + end - File.mkdir_p(Path.dirname(snapshot_file)) - create_file(snapshot_file, snapshot_binary, force: true) + File.mkdir_p(Path.dirname(snapshot_file)) - old_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}#{dev}.json") + old_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}#{dev}.json") - if File.exists?(old_snapshot_folder) do - new_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}/initial#{dev}.json") - File.rename(old_snapshot_folder, new_snapshot_folder) - end - end) - end + if File.exists?(old_snapshot_folder) do + new_snapshot_folder = Path.join(snapshot_folder, "#{snapshot.table}/initial#{dev}.json") + File.rename(old_snapshot_folder, new_snapshot_folder) + end + + {snapshot_file, snapshot_binary} + end) end @doc false @@ -2961,13 +2937,13 @@ defmodule AshPostgres.MigrationGenerator do defp renaming?(table, removing, opts) do if opts.dev do + false + else if opts.no_shell? do raise "Unimplemented: cannot determine: Are you renaming #{table}.#{removing.source}? without shell input" else yes?(opts, "Are you renaming #{table}.#{removing.source}?") end - else - false end end diff --git a/mix.exs b/mix.exs index fb84295b..6cb94ca9 100644 --- a/mix.exs +++ b/mix.exs @@ -166,7 +166,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + # {:ash, ash_version("~> 3.4 and >= 3.4.69")}, + {:ash, github: "ash-project/ash", override: true}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.6", optional: true}, {:ecto_sql, "~> 3.12"}, @@ -189,24 +190,24 @@ defmodule AshPostgres.MixProject do ] end - defp ash_version(default_version) do - case System.get_env("ASH_VERSION") do - nil -> - default_version + # defp ash_version(default_version) do + # case System.get_env("ASH_VERSION") do + # nil -> + # default_version - "local" -> - [path: "../ash", override: true] + # "local" -> + # [path: "../ash", override: true] - "main" -> - [git: "https://github.com/ash-project/ash.git", override: true] + # "main" -> + # [git: "https://github.com/ash-project/ash.git", override: true] - version when is_binary(version) -> - "~> #{version}" + # version when is_binary(version) -> + # "~> #{version}" - version -> - version - end - end + # version -> + # version + # end + # end defp ash_sql_version(default_version) do case System.get_env("ASH_SQL_VERSION") do diff --git a/mix.lock b/mix.lock index 4076ca19..89d5b5af 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.12", "435a6916d47e4ed6eabce886de2443d17af9dd9ca9765a29c73d9e02507b86b3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.60 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "503492989c56e33c300d731b3717d1df9eeebba2b9018e5dd9f330db727edb57"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "e7b1a738644e1092ed350115ebebf5da8ac2aec1", []}, "ash_sql": {:hex, :ash_sql, "0.2.76", "4afac3284194f3d7820b7edc1263ac1eb232a25734406ad7b2284683b9384205", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f6bc02d8c4cba3f8f9a532e6ff73eaf8a4f7685257646a58fe7a320cfaf10c39"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 6a4d0ef5..afbd4ed8 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1420,15 +1420,15 @@ defmodule AshPostgres.MigrationGeneratorTest do [domain: Domain] end - test "returns code(1) if snapshots and resources don't fit", %{domain: domain} do - assert catch_exit( - AshPostgres.MigrationGenerator.generate(domain, - snapshot_path: "test_snapshots_path", - migration_path: "test_migration_path", - check: true, - auto_name: true - ) - ) == {:shutdown, 1} + test "raises an error on pending codegen", %{domain: domain} do + assert_raise Ash.Error.Framework.PendingCodegen, fn -> + AshPostgres.MigrationGenerator.generate(domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + check: true, + auto_name: true + ) + end refute File.exists?(Path.wildcard("test_migration_path2/**/*_migrate_resources*.exs")) refute File.exists?(Path.wildcard("test_snapshots_path2/test_repo/posts/*.json")) From 0f21c885f7072daf317108112ed83f31d3e5f93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Fri, 30 May 2025 18:33:51 +0200 Subject: [PATCH 1061/1215] chore: Bump erlang elixir version in tool versions (#559) --- .tool-versions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index 005db769..32823875 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 27.0.1 -elixir 1.18.4-otp-27 +erlang 27.1.2 +elixir 1.18.4 \ No newline at end of file From de093d8f5683d5b9d848be0fb00244a4e06e6939 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 May 2025 01:33:29 -0400 Subject: [PATCH 1062/1215] chore: better dev mode check --- lib/migration_generator/migration_generator.ex | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index de411a97..58a0ad71 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -456,9 +456,12 @@ defmodule AshPostgres.MigrationGenerator do if !opts.dev and dev_migrations != [] do if opts.check do Mix.shell().error(""" - Generated migrations are from dev mode. + Codegen check failed. - Generate migrations without `--dev` flag. + You have migrations remaining that were generated with the --dev flag. + + Run `mix ash.codegen ` to remove the dev migraitons and replace them + with production ready migrations. """) exit({:shutdown, 1}) From a6c2f2e18bd78dec40aa45acf8dcf7dead4b23e7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 May 2025 16:08:16 -0400 Subject: [PATCH 1063/1215] chore: update deps --- mix.exs | 31 +++++++++++++++---------------- mix.lock | 6 +++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/mix.exs b/mix.exs index 6cb94ca9..91eb50a7 100644 --- a/mix.exs +++ b/mix.exs @@ -166,8 +166,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:ash, ash_version("~> 3.4 and >= 3.4.69")}, - {:ash, github: "ash-project/ash", override: true}, + {:ash, ash_version("~> 3.5 and >= 3.5.13")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.6", optional: true}, {:ecto_sql, "~> 3.12"}, @@ -190,24 +189,24 @@ defmodule AshPostgres.MixProject do ] end - # defp ash_version(default_version) do - # case System.get_env("ASH_VERSION") do - # nil -> - # default_version + defp ash_version(default_version) do + case System.get_env("ASH_VERSION") do + nil -> + default_version - # "local" -> - # [path: "../ash", override: true] + "local" -> + [path: "../ash", override: true] - # "main" -> - # [git: "https://github.com/ash-project/ash.git", override: true] + "main" -> + [git: "https://github.com/ash-project/ash.git", override: true] - # version when is_binary(version) -> - # "~> #{version}" + version when is_binary(version) -> + "~> #{version}" - # version -> - # version - # end - # end + version -> + version + end + end defp ash_sql_version(default_version) do case System.get_env("ASH_SQL_VERSION") do diff --git a/mix.lock b/mix.lock index 89d5b5af..1f3910ec 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "e7b1a738644e1092ed350115ebebf5da8ac2aec1", []}, + "ash": {:hex, :ash, "3.5.13", "1e2d15188dcf01a00f7c91180f6a0fb990c427828fdf9f014b8a6dde951f46a0", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.61 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bdd52d10f95af357f37b61b74636226bf7c1082c4b815df94443d2aa2607c961"}, "ash_sql": {:hex, :ash_sql, "0.2.76", "4afac3284194f3d7820b7edc1263ac1eb232a25734406ad7b2284683b9384205", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f6bc02d8c4cba3f8f9a532e6ff73eaf8a4f7685257646a58fe7a320cfaf10c39"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.3", "8bfaf5955ce83301da0f0a53455f73a0bc4dc5aacd6c311363089850a5dc2dd7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "95d34d8280dea992e05dcbf9865414d69e421a76d743661eaf1d1337ea54fa80"}, + "igniter": {:hex, :igniter, "0.6.4", "81b7442be9ae0b9107b715144f3633f72788644d13ffded612cb508861b2c726", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "e999fc4b883e5eacc07ca2823273880ab30108c7e6a0d1542a3c8e00605aef46"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -38,7 +38,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.15.3", "f1f05d5b0f229ad1a164b7a5543beee58c9975e7e146afc71821e5afc5c69a79", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "8a16a46163fcdeb1d1be06749f54bd71282126be27c9dc80010cf3b97fe7193c"}, + "reactor": {:hex, :reactor, "0.15.4", "ef0c56a901c132529a14ab59fed0ccb4fcecb24308fb189a94c908255d4fdafc", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "783bf62fd0c72ded033afabdb8b6190b7048769771a2a97256e6f0bf4fb0a891"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, From ea9d09c8e675deac12b91f01ea110a58fe43b365 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 May 2025 16:08:24 -0400 Subject: [PATCH 1064/1215] chore: release version v2.6.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63141775..b739ea7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.0](https://github.com/ash-project/ash_postgres/compare/v2.5.22...v2.6.0) (2025-05-30) + + + + +### Features: + +* --dev flag for codegen (#555) + +### Bug Fixes: + +* properly encode decimal scale & preicison into snapshots + +### Improvements: + +* use new `PendingCodegen` error + +* assume not renaming when generating dev migrations + +* support scale & precision in decimal types + ## [v2.5.22](https://github.com/ash-project/ash_postgres/compare/v2.5.21...v2.5.22) (2025-05-22) diff --git a/mix.exs b/mix.exs index 91eb50a7..306c04c6 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.5.22" + @version "2.6.0" def project do [ From be48da96af1d08614d725053b9ea8c088aee79b8 Mon Sep 17 00:00:00 2001 From: Frank Dugan III Date: Fri, 30 May 2025 17:03:44 -0500 Subject: [PATCH 1065/1215] fix: retain repo as atom in migrator task (#560) --- lib/mix/tasks/ash_postgres.migrate.ex | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 3299fc5b..f8ab1d20 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -130,16 +130,9 @@ defmodule Mix.Tasks.AshPostgres.Migrate do for tenant <- tenants(repo, opts) do rest_opts = AshPostgres.Mix.Helpers.delete_arg(rest_opts, "--prefix") - repo = - if is_atom(repo) do - inspect(repo) - else - repo - end - Mix.Task.run( "ecto.migrate", - ["-r", repo] ++ + ["-r", to_string(repo)] ++ rest_opts ++ ["--prefix", tenant, "--migrations-path", tenant_migrations_path(opts, repo)] ) From 7f91cc87e8f0106c593dce6d9f8745fe9b71e733 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 May 2025 18:04:09 -0400 Subject: [PATCH 1066/1215] chore: release version v2.6.1 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b739ea7e..f627e950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.1](https://github.com/ash-project/ash_postgres/compare/v2.6.0...v2.6.1) (2025-05-30) + + + + +### Bug Fixes: + +* retain repo as atom in migrator task (#560) + ## [v2.6.0](https://github.com/ash-project/ash_postgres/compare/v2.5.22...v2.6.0) (2025-05-30) diff --git a/mix.exs b/mix.exs index 306c04c6..036bf575 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.0" + @version "2.6.1" def project do [ From 57dbdb4bf52d4d27ec87b831a09ee7190c291a24 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 2 Jun 2025 17:24:44 -0400 Subject: [PATCH 1067/1215] test: add tests corresponding to #561 unable to reproduce --- test/atomics_test.exs | 21 +++++++++++++++++++++ test/support/resources/post.ex | 10 ++++++++++ 2 files changed, 31 insertions(+) diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 301cf8e3..2439ab9d 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -40,6 +40,27 @@ defmodule AshPostgres.AtomicsTest do |> Ash.update!() end + test "an atomic update on decimals works" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "foo", decimal: Decimal.new("1")}) + |> Ash.create!() + + assert %{decimal: result} = + post + |> Ash.Changeset.for_update(:subtract_integer_from_decimal, %{amount: 2}) + |> Ash.update!() + + assert Decimal.eq?(result, Decimal.new("-1")) + + assert %{decimal: result} = + post + |> Ash.Changeset.for_update(:subtract_from_decimal, %{amount: Decimal.new("2")}) + |> Ash.update!() + + assert Decimal.eq?(result, Decimal.new("-3")) + end + test "an atomic works on a constrained integer" do post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c66c8680..4b893048 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -184,6 +184,16 @@ defmodule AshPostgres.Test.Post do end) end + update :subtract_integer_from_decimal do + argument(:amount, :integer, allow_nil?: false) + change(atomic_update(:decimal, expr(decimal + -(^arg(:amount))))) + end + + update :subtract_from_decimal do + argument(:amount, :decimal, allow_nil?: false) + change(atomic_update(:decimal, expr(decimal + -(^arg(:amount))))) + end + update :add_to_limited_score do argument(:amount, :integer, allow_nil?: false) change(atomic_update(:limited_score, expr((limited_score || 0) + ^arg(:amount)))) From b7d69500ec8fb7fa6cd7a1900b88a0ac41a9e841 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Jun 2025 22:32:55 -0400 Subject: [PATCH 1068/1215] chore: remove ash added content when running query --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 40227e7f..8196cbe7 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1532,7 +1532,7 @@ defmodule AshPostgres.DataLayer do {_, results} = with_savepoint(repo, query, fn -> repo.update_all( - query, + Map.delete(query, :__ash_bindings__), [], repo_opts ) From afa872b660fcac55dcb50900c19ca21cface7d8f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 3 Jun 2025 22:49:31 -0400 Subject: [PATCH 1069/1215] chore: cleanup community files --- .github/CODE_OF_CONDUCT.md | 76 ----------------------- .github/CONTRIBUTING.md | 2 - .github/ISSUE_TEMPLATE/bug_report.md | 27 -------- .github/ISSUE_TEMPLATE/feature_request.md | 35 ----------- .github/PULL_REQUEST_TEMPLATE.md | 4 -- 5 files changed, 144 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 7aa6f743..00000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at zach@zachdaniel.dev. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index f537454f..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,2 +0,0 @@ -# Contributing Guidelines -Contributing guidelines can be found in the core project, [ash](https://github.com/ash-project/ash/blob/main/.github/CONTRIBUTING.md) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 1f47341d..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug, needs review -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. If you are not sure if the bug is related to `ash` or an extension, log it with [ash](https://github.com/ash-project/ash/issues/new) and we will move it. - -**To Reproduce** -A minimal set of resource definitions and calls that can reproduce the bug. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -** Runtime - - Elixir version - - Erlang version - - OS - - Ash version - - any related extension versions - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index a6442e0d..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Proposal -about: Suggest an idea for this project -title: "" -labels: enhancement, needs review -assignees: "" ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Express the feature either with a change to resource syntax, or with a change to the resource interface** - -For example - -```elixir - attributes do - attribute :foo, :integer, bar: 10 # <- Adding `bar` here would cause - end -``` - -Or - -```elixir - Ash.read(Resource, bar: 10) # <- Adding `bar` here would cause -``` - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 8c13744f..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,4 +0,0 @@ -### Contributor checklist - -- [ ] Bug fixes include regression tests -- [ ] Features include unit/acceptance tests From 3e24e7c8970547b710020fec62918bd3bbfedb5e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Jun 2025 09:49:24 -0400 Subject: [PATCH 1070/1215] fix: don't use `:"timestamptz(6)"` in ecto storage type --- lib/migration_generator/migration_generator.ex | 7 ++++++- lib/type.ex | 5 ++++- lib/types/timestamptz.ex | 4 ---- lib/types/timestamptz_usec.ex | 6 ++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 58a0ad71..1699141d 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3364,8 +3364,13 @@ defmodule AshPostgres.MigrationGenerator do defp migration_type(other, constraints) do type = Ash.Type.get_type(other) + Code.ensure_loaded(type) - migration_type_from_storage_type(Ash.Type.storage_type(type, constraints)) + if function_exported?(type, :migration_type, 1) do + type.migration_type(constraints) + else + migration_type_from_storage_type(Ash.Type.storage_type(type, constraints)) + end end defp migration_type_from_storage_type(:string), do: :text diff --git a/lib/type.ex b/lib/type.ex index a437057e..22a3ec07 100644 --- a/lib/type.ex +++ b/lib/type.ex @@ -11,8 +11,11 @@ defmodule AshPostgres.Type do @callback postgres_reference_expr(Ash.Type.t(), Ash.Type.constraints(), term) :: {:ok, term} | :error + @callback migration_type(Ash.Type.constraints()) :: term() + @optional_callbacks value_to_postgres_default: 3, - postgres_reference_expr: 3 + postgres_reference_expr: 3, + migration_type: 1 defmacro __using__(_) do quote do diff --git a/lib/types/timestamptz.ex b/lib/types/timestamptz.ex index dfac2d4f..11b007a3 100644 --- a/lib/types/timestamptz.ex +++ b/lib/types/timestamptz.ex @@ -28,10 +28,6 @@ defmodule AshPostgres.Timestamptz do attribute :timestamp, :timestamptz timestamps type: :timestamptz ``` - - - - """ use Ash.Type.NewType, subtype_of: :datetime, constraints: [precision: :second] diff --git a/lib/types/timestamptz_usec.ex b/lib/types/timestamptz_usec.ex index 513d455d..d723a2dd 100644 --- a/lib/types/timestamptz_usec.ex +++ b/lib/types/timestamptz_usec.ex @@ -21,14 +21,16 @@ defmodule AshPostgres.TimestamptzUsec do timestamps type: :timestamptz_usec ``` - - Please see `AshPostgres.Timestamptz` for details about the usecase for this type. """ use Ash.Type.NewType, subtype_of: :datetime, constraints: [precision: :microsecond] @impl true def storage_type(_constraints) do + :timestamptz + end + + def migration_type(_constraints) do :"timestamptz(6)" end end From 9c6e40426eab9f93a4688fc0b9af4f2d3336e990 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Jun 2025 11:05:13 -0400 Subject: [PATCH 1071/1215] chore: release version v2.6.2 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f627e950..7280df90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.2](https://github.com/ash-project/ash_postgres/compare/v2.6.1...v2.6.2) (2025-06-04) + + + + +### Bug Fixes: + +* don't use `:"timestamptz(6)"` in ecto storage type + ## [v2.6.1](https://github.com/ash-project/ash_postgres/compare/v2.6.0...v2.6.1) (2025-05-30) diff --git a/mix.exs b/mix.exs index 036bf575..e260f2fd 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.1" + @version "2.6.2" def project do [ From 3995ba30cf8eda1faf748785280f4d12dd194cd9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Jun 2025 13:16:57 -0400 Subject: [PATCH 1072/1215] fix: undo change for timestamptz usec, retaining precision --- lib/types/timestamptz_usec.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/types/timestamptz_usec.ex b/lib/types/timestamptz_usec.ex index d723a2dd..f1dae8fd 100644 --- a/lib/types/timestamptz_usec.ex +++ b/lib/types/timestamptz_usec.ex @@ -27,10 +27,6 @@ defmodule AshPostgres.TimestamptzUsec do @impl true def storage_type(_constraints) do - :timestamptz - end - - def migration_type(_constraints) do :"timestamptz(6)" end end From 1492fc5596b0e6aaf019294f835db72141633dad Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Jun 2025 13:39:41 -0400 Subject: [PATCH 1073/1215] chore: stabilize migration tests --- test/dev_migrations_test.exs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/dev_migrations_test.exs b/test/dev_migrations_test.exs index 0f7b93c0..400bca14 100644 --- a/test/dev_migrations_test.exs +++ b/test/dev_migrations_test.exs @@ -94,7 +94,7 @@ defmodule AshPostgres.DevMigrationsTest do do: File.ls!(tenant_migrations_dev_path), else: [] - on_exit(fn -> + clean = fn -> if File.exists?(resource_dev_path) do current_resource_files = File.ls!(resource_dev_path) new_resource_files = current_resource_files -- initial_resource_files @@ -120,7 +120,11 @@ defmodule AshPostgres.DevMigrationsTest do end AshPostgres.DevTestRepo.query!("DROP TABLE IF EXISTS posts") - end) + end + + clean.() + + on_exit(clean) end describe "--dev option" do From 6238948e5cfbe54bdc47b13959c10d7a30765eb7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 4 Jun 2025 13:40:16 -0400 Subject: [PATCH 1074/1215] chore: release version v2.6.3 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7280df90..b5f5faed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.3](https://github.com/ash-project/ash_postgres/compare/v2.6.2...v2.6.3) (2025-06-04) + + + + +### Bug Fixes: + +* undo change for timestamptz usec, retaining precision + ## [v2.6.2](https://github.com/ash-project/ash_postgres/compare/v2.6.1...v2.6.2) (2025-06-04) diff --git a/mix.exs b/mix.exs index e260f2fd..e13172bb 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.2" + @version "2.6.3" def project do [ From 2b5011369595a5a64d85bd99d7de7209fd0aa0fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:01:53 -0400 Subject: [PATCH 1075/1215] chore(deps): bump the production-dependencies group with 3 updates (#562) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.5.13 to 3.5.15 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.13...v3.5.15) Updates `ash_sql` from 0.2.76 to 0.2.77 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.76...v0.2.77) Updates `igniter` from 0.6.4 to 0.6.5 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.6.4...v0.6.5) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-version: 0.2.77 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-version: 0.6.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 1f3910ec..751ea5f0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.13", "1e2d15188dcf01a00f7c91180f6a0fb990c427828fdf9f014b8a6dde951f46a0", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.61 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bdd52d10f95af357f37b61b74636226bf7c1082c4b815df94443d2aa2607c961"}, - "ash_sql": {:hex, :ash_sql, "0.2.76", "4afac3284194f3d7820b7edc1263ac1eb232a25734406ad7b2284683b9384205", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f6bc02d8c4cba3f8f9a532e6ff73eaf8a4f7685257646a58fe7a320cfaf10c39"}, + "ash": {:hex, :ash, "3.5.15", "418055e74c4d85d1f397577407aa71c29d44ef0b729be144e23e64630ad2eedc", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.61 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8ee885ccc6be65d1023cccbdfda6a534497cf5170d53b67c0b29db72c0c30b9b"}, + "ash_sql": {:hex, :ash_sql, "0.2.77", "731c87a624ffe2bed61de8fed4d11aacc5057ceedbe1df076979e9b6bc3fd499", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a94f9be7e6583f193dbb1705192c3ddee6a49ff5a870ddeb6cd3a9a947add0db"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.4", "81b7442be9ae0b9107b715144f3633f72788644d13ffded612cb508861b2c726", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "e999fc4b883e5eacc07ca2823273880ab30108c7e6a0d1542a3c8e00605aef46"}, + "igniter": {:hex, :igniter, "0.6.5", "0b16a37e1aaaefc39777c6250980a314df8ba02a8ae81063d786a7bddb40dbf0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "21dec3066f372f49f391d00a2067769eb20f7a2213513e022593e4b51bad93e2"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -45,7 +45,7 @@ "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spark": {:hex, :spark, "2.2.62", "610502559834465edce437de712bf7e6d59713ad48050789dbef69a798e71a3c", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "726df72e1b9c17401584b4657e75e08a27a1cf6a6effa2486bf1c074da6176a7"}, - "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, + "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, From 7c8b89a6eeb28ddd69535b553b91b0a143a6ed81 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Jun 2025 13:38:51 -0400 Subject: [PATCH 1076/1215] chore: add test --- test/calculation_test.exs | 16 +++++++++++++++- test/support/resources/record.ex | 14 +++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 017ca25a..a8c1703f 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.CalculationTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Account, Author, Comedian, Comment, Post, User} + alias AshPostgres.Test.{Account, Author, Comedian, Comment, Post, Record, TempEntity, User} require Ash.Query import Ash.Expr @@ -1016,4 +1016,18 @@ defmodule AshPostgres.CalculationTest do def fred do "fred" end + + test "calculation references use the appropriate schema" do + record = Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + + TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + + full_name = + Record + |> Ash.Query.load(:temp_entity_full_name) + |> Ash.read_first!() + |> Map.get(:temp_entity_full_name) + + assert full_name == "name" + end end diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index b98ef59e..db820fca 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -14,9 +14,7 @@ defmodule AshPostgres.Test.Record do end relationships do - alias AshPostgres.Test.Entity - - has_one :entity, Entity do + has_one :entity, AshPostgres.Test.Entity do public?(true) no_attributes?(true) @@ -24,6 +22,12 @@ defmodule AshPostgres.Test.Record do filter(expr(full_name == parent(full_name))) end + + has_one :temp_entity, AshPostgres.Test.TempEntity do + public?(true) + source_attribute(:full_name) + destination_attribute(:full_name) + end end postgres do @@ -31,6 +35,10 @@ defmodule AshPostgres.Test.Record do repo AshPostgres.TestRepo end + calculations do + calculate(:temp_entity_full_name, :string, expr(temp_entity.full_name)) + end + actions do default_accept(:*) From 5e21f8946ed7fa01c4e541fedf52fc0dd6120720 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 5 Jun 2025 14:51:58 -0400 Subject: [PATCH 1077/1215] test: add test for ash_sql datetime time zone issues --- test/calculation_test.exs | 2 +- test/complex_calculations_test.exs | 27 +++++++++++++++++++ .../resources/documentation.ex | 11 ++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index a8c1703f..4b6fff35 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1018,7 +1018,7 @@ defmodule AshPostgres.CalculationTest do end test "calculation references use the appropriate schema" do - record = Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 73fd689d..83cb39cf 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -346,4 +346,31 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do |> Ash.Query.for_read(:failing_many_reference) |> Ash.read!(page: [count: true]) end + + test "lazy datetime with timezone is respected in calculations" do + documentation_before = + AshPostgres.Test.ComplexCalculations.Documentation + |> Ash.Changeset.for_create(:create, %{ + status: :demonstrated, + # 2 hours before Seoul time in UTC + inserted_at: ~U[2024-05-01 10:00:00Z] + }) + |> Ash.create!() + + documentation_after = + AshPostgres.Test.ComplexCalculations.Documentation + |> Ash.Changeset.for_create(:create, %{ + status: :demonstrated, + # 2 hours after Seoul time in UTC + inserted_at: ~U[2024-05-01 14:00:00Z] + }) + |> Ash.create!() + + [doc_before, doc_after] = + [documentation_before, documentation_after] + |> Ash.load!([:is_active_with_timezone]) + + assert doc_before.is_active_with_timezone == false + assert doc_after.is_active_with_timezone == true + end end diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex index ad961895..df6c5415 100644 --- a/test/support/complex_calculations/resources/documentation.ex +++ b/test/support/complex_calculations/resources/documentation.ex @@ -42,6 +42,10 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do end ) ) + + calculate :is_active_with_timezone, :boolean do + calculation(expr(inserted_at > lazy({AshPostgres.Test.TimezoneHelper, :seoul_time, []}))) + end end postgres do @@ -55,3 +59,10 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do end end end + +defmodule AshPostgres.Test.TimezoneHelper do + def seoul_time do + # Fixed datetime for testing - equivalent to 2024-05-01 21:00:00 in Seoul (UTC+9) + ~U[2024-05-01 12:00:00Z] |> DateTime.shift_zone!("Asia/Seoul") + end +end From 5b8f46ffb52378a818dee7278d9ab9702dc12121 Mon Sep 17 00:00:00 2001 From: kernel-io Date: Fri, 6 Jun 2025 12:39:56 +1200 Subject: [PATCH 1078/1215] test: failing test for calculations in different schema (#563) * failing test for calculations in different schema Signed-off-by: kernel-io * fix test to not require grouping Signed-off-by: kernel-io --------- Signed-off-by: kernel-io --- .../records_temp_entities/20250605230457.json | 93 +++++++++++++++++++ ...0457_create_record_temp_entities_table.exs | 43 +++++++++ test/calculation_test.exs | 8 +- test/support/domain.ex | 1 + test/support/resources/record.ex | 12 ++- test/support/resources/record_temp_entity.ex | 25 +++++ 6 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json create mode 100644 priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs create mode 100644 test/support/resources/record_temp_entity.ex diff --git a/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json b/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json new file mode 100644 index 00000000..af6e3c23 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json @@ -0,0 +1,93 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "records_temp_entities_record_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "records" + }, + "scale": null, + "size": null, + "source": "record_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "records_temp_entities_temp_entity_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "temp", + "table": "temp_entities" + }, + "scale": null, + "size": null, + "source": "temp_entity_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "1F00231E86F4E276096E683FD2836B40F36C6AD617A777D947E908FD52D09FDB", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "records_temp_entities" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs b/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs new file mode 100644 index 00000000..fe910c8c --- /dev/null +++ b/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs @@ -0,0 +1,43 @@ +defmodule AshPostgres.TestRepo.Migrations.CreateRecordTempEntitiesTable 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 + create table(:records_temp_entities, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + + add( + :record_id, + references(:records, + column: :id, + name: "records_temp_entities_record_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + + add( + :temp_entity_id, + references(:temp_entities, + column: :id, + name: "records_temp_entities_temp_entity_id_fkey", + type: :uuid, + prefix: "temp" + ) + ) + end + end + + def down do + drop(constraint(:records_temp_entities, "records_temp_entities_record_id_fkey")) + + drop(constraint(:records_temp_entities, "records_temp_entities_temp_entity_id_fkey")) + + drop(table(:records_temp_entities)) + end +end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 4b6fff35..6aa911fe 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,4 +1,5 @@ defmodule AshPostgres.CalculationTest do + alias AshPostgres.Test.RecordTempEntity use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Account, Author, Comedian, Comment, Post, Record, TempEntity, User} @@ -1018,9 +1019,12 @@ defmodule AshPostgres.CalculationTest do end test "calculation references use the appropriate schema" do - Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + record = Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() - TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + temp_entity = + TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!() + + Ash.Seed.seed!(RecordTempEntity, %{record_id: record.id, temp_entity_id: temp_entity.id}) full_name = Record diff --git a/test/support/domain.ex b/test/support/domain.ex index 25616374..c8c596d5 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -31,6 +31,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Entity) resource(AshPostgres.Test.ContentVisibilityGroup) resource(AshPostgres.Test.TempEntity) + resource(AshPostgres.Test.RecordTempEntity) resource(AshPostgres.Test.Permalink) resource(AshPostgres.Test.Record) resource(AshPostgres.Test.PostFollower) diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index db820fca..804c981a 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -28,6 +28,12 @@ defmodule AshPostgres.Test.Record do source_attribute(:full_name) destination_attribute(:full_name) end + + many_to_many :temp_entities, AshPostgres.Test.TempEntity do + public?(true) + + through(AshPostgres.Test.RecordTempEntity) + end end postgres do @@ -36,7 +42,11 @@ defmodule AshPostgres.Test.Record do end calculations do - calculate(:temp_entity_full_name, :string, expr(temp_entity.full_name)) + calculate( + :temp_entity_full_name, + :string, + expr(fragment("coalesce(?, '')", temp_entities.full_name)) + ) end actions do diff --git a/test/support/resources/record_temp_entity.ex b/test/support/resources/record_temp_entity.ex new file mode 100644 index 00000000..7750a1df --- /dev/null +++ b/test/support/resources/record_temp_entity.ex @@ -0,0 +1,25 @@ +defmodule AshPostgres.Test.RecordTempEntity do + @moduledoc false + + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "records_temp_entities" + repo AshPostgres.TestRepo + end + + attributes do + uuid_primary_key(:id) + end + + relationships do + belongs_to(:record, AshPostgres.Test.Record, public?: true) + belongs_to(:temp_entity, AshPostgres.Test.TempEntity, public?: true) + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end +end From db011c06900cd24f320dfe5b938a685730497eb0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 7 Jun 2025 15:05:26 -0400 Subject: [PATCH 1079/1215] fix: use better wrappers around string/ci_string --- lib/types/ci_string_wrapper.ex | 9 +-------- lib/types/string_wrapper.ex | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/types/ci_string_wrapper.ex b/lib/types/ci_string_wrapper.ex index 20a9b12e..74e47347 100644 --- a/lib/types/ci_string_wrapper.ex +++ b/lib/types/ci_string_wrapper.ex @@ -1,14 +1,7 @@ defmodule AshPostgres.Type.CiStringWrapper do @moduledoc false - use Ash.Type + use Ash.Type.NewType, subtype_of: :ci_string, constraints: [allow_empty?: true, trim?: false] @impl true def storage_type(_), do: :citext - - @impl true - defdelegate cast_input(value, constraints), to: Ash.Type.CiString - @impl true - defdelegate cast_stored(value, constraints), to: Ash.Type.CiString - @impl true - defdelegate dump_to_native(value, constraints), to: Ash.Type.CiString end diff --git a/lib/types/string_wrapper.ex b/lib/types/string_wrapper.ex index e6101c52..84b5cd2c 100644 --- a/lib/types/string_wrapper.ex +++ b/lib/types/string_wrapper.ex @@ -1,14 +1,7 @@ defmodule AshPostgres.Type.StringWrapper do @moduledoc false - use Ash.Type + use Ash.Type.NewType, subtype_of: :string, constraints: [allow_empty?: true, trim?: false] @impl true def storage_type(_), do: :text - - @impl true - defdelegate cast_input(value, constraints), to: Ash.Type.String - @impl true - defdelegate cast_stored(value, constraints), to: Ash.Type.String - @impl true - defdelegate dump_to_native(value, constraints), to: Ash.Type.String end From 12579ad3e79418a996dbddf7462b4e3d4e67e158 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 8 Jun 2025 01:02:44 -0400 Subject: [PATCH 1080/1215] chore: remove deprecated syntax --- lib/sql_implementation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 6648a7e4..a91b8786 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -188,7 +188,7 @@ defmodule AshPostgres.SqlImplementation do type ) do if function_exported?(attr_type, :postgres_reference_expr, 3) do - non_bare_ref = %Ash.Query.Ref{ref | bare?: nil} + non_bare_ref = %{ref | bare?: nil} {expr, acc} = AshSql.Expr.dynamic_expr(query, non_bare_ref, bindings, embedded?, type, acc) case attr_type.postgres_reference_expr(attr_type, constraints, expr) do From 5c15c7e70e15f34f8fa81816ba2711519cb335c2 Mon Sep 17 00:00:00 2001 From: "Stephan Hug (FlyingNoodle)" <88476449+StephanH90@users.noreply.github.com> Date: Sun, 8 Jun 2025 07:04:06 +0200 Subject: [PATCH 1081/1215] fix: casting integers to string in expressions works as intended (#564) This commit currently only adds a failing test. --- test/support/resources/post.ex | 6 ++++++ test/type_test.exs | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 4b893048..369283bc 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -154,6 +154,12 @@ defmodule AshPostgres.Test.Post do defaults([:read, :destroy]) + read :with_version_check do + argument(:version, :integer) + + filter(expr(type(^arg(:version), :string) in ["1", "2"])) + end + read :first_and_last_post do prepare(fn query, _ -> Ash.Query.combination_of(query, [ diff --git a/test/type_test.exs b/test/type_test.exs index 428fcab1..4c3731e7 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -100,4 +100,11 @@ defmodule AshPostgres.Test.TypeTest do assert %{x: 2.0, y: 3.0, z: 4.0} = p.db_string_point_id assert %{x: 2.0, y: 3.0, z: 4.0} = p.db_string_point.id end + + test "casting integer to string works" do + Post |> Ash.Changeset.for_create(:create) |> Ash.create!() + + post = Ash.Query.for_read(Post, :with_version_check, version: 1) |> Ash.read!() + refute is_nil(post) + end end From cf3da36618ca27c069386bb35b61d4337752a433 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 8 Jun 2025 01:08:28 -0400 Subject: [PATCH 1082/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 751ea5f0..0fd35a2e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.15", "418055e74c4d85d1f397577407aa71c29d44ef0b729be144e23e64630ad2eedc", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.61 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8ee885ccc6be65d1023cccbdfda6a534497cf5170d53b67c0b29db72c0c30b9b"}, - "ash_sql": {:hex, :ash_sql, "0.2.77", "731c87a624ffe2bed61de8fed4d11aacc5057ceedbe1df076979e9b6bc3fd499", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a94f9be7e6583f193dbb1705192c3ddee6a49ff5a870ddeb6cd3a9a947add0db"}, + "ash_sql": {:hex, :ash_sql, "0.2.78", "130d54ca85b8fbd1e65f038beafb905ade1941d1f5e3aef1fec7f8bba7414fc2", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a640ec0ef176f3a0e95f88d8639d9d23479ca8a5f5e7e177af351f18cddf4f30"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.5", "0b16a37e1aaaefc39777c6250980a314df8ba02a8ae81063d786a7bddb40dbf0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "21dec3066f372f49f391d00a2067769eb20f7a2213513e022593e4b51bad93e2"}, + "igniter": {:hex, :igniter, "0.6.6", "82d707a2419a95e6ea115949c68a9113dfc0b4802d3d8386aa351feb0ead71ee", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a85832987fc78f5fdc38f628a62acfd50b4e441166496fea15c7b05218fa84f5"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -44,7 +44,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.62", "610502559834465edce437de712bf7e6d59713ad48050789dbef69a798e71a3c", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "726df72e1b9c17401584b4657e75e08a27a1cf6a6effa2486bf1c074da6176a7"}, + "spark": {:hex, :spark, "2.2.63", "bc771c3c1028136559507e235e721e730118ca316e22e75da347ae1c195b94a1", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "42708ab2884af6abc1aa33aa981a4f51f65146d7879651d1e6c11948a4d8fcfe"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 2c6466d7d0576815bfe01e5d2231b3a6c8440a22 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 9 Jun 2025 10:59:29 -0400 Subject: [PATCH 1083/1215] improvement: add `c:AshPostgres.Repo.create_schemas_in_migrations?` callback --- lib/migration_generator/migration_generator.ex | 11 +++++++++-- lib/migration_generator/operation.ex | 2 +- lib/migration_generator/phase.ex | 12 +++++++++--- lib/repo.ex | 5 +++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 1699141d..129a7ddd 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1361,14 +1361,20 @@ defmodule AshPostgres.MigrationGenerator do defp group_into_phases( [ - %Operation.CreateTable{table: table, schema: schema, multitenancy: multitenancy} | rest + %Operation.CreateTable{ + table: table, + schema: schema, + multitenancy: multitenancy, + repo: repo + } + | rest ], nil, acc ) do group_into_phases( rest, - %Phase.Create{table: table, schema: schema, multitenancy: multitenancy}, + %Phase.Create{table: table, schema: schema, multitenancy: multitenancy, repo: repo}, acc ) end @@ -2010,6 +2016,7 @@ defmodule AshPostgres.MigrationGenerator do %Operation.CreateTable{ table: snapshot.table, schema: snapshot.schema, + repo: snapshot.repo, multitenancy: snapshot.multitenancy, old_multitenancy: empty_snapshot.multitenancy } diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 242a5db1..5c8bfbc0 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -145,7 +145,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do defmodule CreateTable do @moduledoc false - defstruct [:table, :schema, :multitenancy, :old_multitenancy] + defstruct [:table, :schema, :multitenancy, :old_multitenancy, :repo] end defmodule AddAttribute do diff --git a/lib/migration_generator/phase.ex b/lib/migration_generator/phase.ex index d743810d..6be7dd81 100644 --- a/lib/migration_generator/phase.ex +++ b/lib/migration_generator/phase.ex @@ -3,18 +3,24 @@ defmodule AshPostgres.MigrationGenerator.Phase do defmodule Create do @moduledoc false - defstruct [:table, :schema, :multitenancy, operations: [], commented?: false] + defstruct [:table, :schema, :multitenancy, :repo, operations: [], commented?: false] import AshPostgres.MigrationGenerator.Operation.Helper, only: [as_atom: 1] - def up(%{schema: schema, table: table, operations: operations, multitenancy: multitenancy}) do + def up(%{ + schema: schema, + table: table, + operations: operations, + multitenancy: multitenancy, + repo: repo + }) do if multitenancy.strategy == :context do "create table(:#{as_atom(table)}, primary_key: false, prefix: prefix()) do\n" <> Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <> "\nend" else {pre_create, opts} = - if schema do + if schema && repo.create_schemas_in_migrations?() do {"execute(\"CREATE SCHEMA IF NOT EXISTS #{schema}\")" <> "\n\n", ", prefix: \"#{schema}\""} else diff --git a/lib/repo.ex b/lib/repo.ex index eb1beebf..fb7cb7eb 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -78,6 +78,9 @@ defmodule AshPostgres.Repo do @doc "The default prefix(postgres schema) to use when building queries" @callback default_prefix() :: String.t() + @doc "Whether or not to create schemas for tables when generating migrations" + @callback create_schemas_in_migrations?() :: boolean() + @doc "Whether or not to explicitly start and close a transaction for each action, even if there are no transaction hooks. Defaults to `true`." @callback prefer_transaction?() :: boolean @@ -135,6 +138,7 @@ defmodule AshPostgres.Repo do def installed_extensions, do: [] def tenant_migrations_path, do: nil def migrations_path, do: nil + def create_schemas_in_migrations?, do: true def default_prefix, do: "public" def override_migration_type(type), do: type def create?, do: true @@ -304,6 +308,7 @@ defmodule AshPostgres.Repo do prefer_transaction?: 0, prefer_transaction_for_atomic_updates?: 0, tenant_migrations_path: 0, + create_schemas_in_migrations?: 0, migrations_path: 0, default_prefix: 0, override_migration_type: 1, From 1c29ac9e3484863e649cd475e6967a0b082f6289 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 9 Jun 2025 12:17:17 -0400 Subject: [PATCH 1084/1215] fix: use `force: true`, not `force?: true` calling mix.generator --- lib/migration_generator/migration_generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 129a7ddd..e00cd4f6 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -86,7 +86,7 @@ defmodule AshPostgres.MigrationGenerator do true -> Enum.each(files, fn {file, contents} -> - Mix.Generator.create_file(file, contents, force?: true) + Mix.Generator.create_file(file, contents, force: true) end) end end From f3465d2308d224f7d1b937c3298f12f43d576d0f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 9 Jun 2025 12:27:15 -0400 Subject: [PATCH 1085/1215] fix: reenable migrate task --- lib/data_layer.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 8196cbe7..10d1a17b 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -419,6 +419,7 @@ defmodule AshPostgres.DataLayer do ] def migrate(args) do + Mix.Task.reenable("ash_postgres.migrate") Mix.Task.run("ash_postgres.migrate", args) end From dc81e68f5fc03fb99f90f0c95d952fdcf8272090 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 9 Jun 2025 12:31:02 -0400 Subject: [PATCH 1086/1215] chore: release version v2.6.4 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f5faed..4b094699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.4](https://github.com/ash-project/ash_postgres/compare/v2.6.3...v2.6.4) (2025-06-09) + + + + +### Bug Fixes: + +* reenable migrate task + +* use `force: true`, not `force?: true` calling mix.generator + +* casting integers to string in expressions works as intended (#564) + +* use better wrappers around string/ci_string + +### Improvements: + +* add `c:AshPostgres.Repo.create_schemas_in_migrations?` callback + ## [v2.6.3](https://github.com/ash-project/ash_postgres/compare/v2.6.2...v2.6.3) (2025-06-04) diff --git a/mix.exs b/mix.exs index e13172bb..ee671a1f 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.3" + @version "2.6.4" def project do [ From 2afe9640c289a78024a6948c0274a9d5fd95c2ec Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Jun 2025 09:18:04 -0400 Subject: [PATCH 1087/1215] fix: properly detect nested array decimals --- lib/migration_generator/migration_generator.ex | 4 ++-- test/migration_generator_test.exs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index e00cd4f6..ba9990ca 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3593,8 +3593,8 @@ defmodule AshPostgres.MigrationGenerator do ["decimal", precision, scale] end - defp sanitize_type(type, size, precision, decimal) when is_atom(type) and is_integer(size) do - [sanitize_type(type, nil, precision, decimal), size] + defp sanitize_type(type, size, precision, decimal) when is_tuple(type) do + sanitize_type(elem(type, 0), size, precision, decimal) end defp sanitize_type(type, _, _, _) do diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index afbd4ed8..bd8f2c92 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -2511,6 +2511,11 @@ defmodule AshPostgres.MigrationGeneratorTest do public?: true ) + attribute(:decimal_list, {:array, :decimal}, + default: [Decimal.new("123.4567890987654321987")], + public?: true + ) + attribute(:name, :string, default: "Fred", public?: true) attribute(:tag, :atom, default: :value, public?: true) attribute(:enabled, :boolean, default: false, public?: true) From e2c0621f2d06d4a63835935a9cedf35b7cfe424a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Jun 2025 11:20:28 -0400 Subject: [PATCH 1088/1215] fix: remove spurios debug logging --- lib/multitenancy.ex | 4 ---- test/dev_migrations_test.exs | 2 -- 2 files changed, 6 deletions(-) diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 30abecbd..6d70aa49 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -22,7 +22,6 @@ defmodule AshPostgres.MultiTenancy do repo.config(), prefix: tenant_name ) - |> tap(fn v -> Logger.warning(inspect(v, label: "ensure migraitons result")) end) [tenant_migrations_path, "**", "*.exs"] |> Path.join() @@ -42,7 +41,6 @@ defmodule AshPostgres.MultiTenancy do |> Enum.map(&extract_migration_info/1) |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) - |> tap(fn v -> Logger.warning(inspect(v, label: "migrations")) end) |> Enum.each(fn {version, mod} -> Ecto.Migration.Runner.run( repo, @@ -55,10 +53,8 @@ defmodule AshPostgres.MultiTenancy do all: true, prefix: tenant_name ) - |> tap(fn v -> Logger.warning(inspect(v, label: "run result")) end) Ecto.Migration.SchemaMigration.up(repo, repo.config(), version, prefix: tenant_name) - |> tap(fn v -> Logger.warning(inspect(v, label: "schema run result")) end) end) end diff --git a/test/dev_migrations_test.exs b/test/dev_migrations_test.exs index 400bca14..fdaeeff9 100644 --- a/test/dev_migrations_test.exs +++ b/test/dev_migrations_test.exs @@ -224,7 +224,6 @@ defmodule AshPostgres.DevMigrationsTest do "priv/dev_test_repo/migrations", after_file ) - |> tap(fn v -> Logger.warning(inspect(v, label: "result")) end) end defp tenant_migrate do @@ -234,7 +233,6 @@ defmodule AshPostgres.DevMigrationsTest do AshPostgres.DevTestRepo, "priv/dev_test_repo/tenant_migrations" ) - |> tap(fn v -> Logger.warning(inspect(v, label: "result")) end) end end end From 17c66b9402a14e588eddd6ed3a8a72d7ab33fb90 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Jun 2025 11:20:38 -0400 Subject: [PATCH 1089/1215] chore: release version v2.6.5 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b094699..1d810f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.5](https://github.com/ash-project/ash_postgres/compare/v2.6.4...v2.6.5) (2025-06-10) + + + + +### Bug Fixes: + +* remove spurios debug logging + +* properly detect nested array decimals + ## [v2.6.4](https://github.com/ash-project/ash_postgres/compare/v2.6.3...v2.6.4) (2025-06-09) diff --git a/mix.exs b/mix.exs index ee671a1f..d1548b95 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.4" + @version "2.6.5" def project do [ From 8d99e9aca932e4dd2cd6428f50d8a0063256ecab Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Jun 2025 19:22:16 -0400 Subject: [PATCH 1090/1215] fix: simply storage of size/scale/precision information --- .../migration_generator.ex | 109 +++++------------- 1 file changed, 31 insertions(+), 78 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index ba9990ca..a5984a67 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3199,25 +3199,7 @@ defmodule AshPostgres.MigrationGenerator do end {type, size, precision, scale} = - case type do - {:varchar, size} -> - {:varchar, size, nil, nil} - - {:binary, size} -> - {:binary, size, nil, nil} - - {:decimal, precision, scale} -> - {:decimal, nil, precision, scale} - - {:decimal, precision} -> - {:decimal, nil, precision, nil} - - {other, size} when is_atom(other) and is_integer(size) -> - {other, size, nil, nil} - - other -> - {other, nil, nil, nil} - end + migration_type_to_type(type) attribute |> Map.put(:default, default) @@ -3247,6 +3229,32 @@ defmodule AshPostgres.MigrationGenerator do end) end + defp migration_type_to_type(type) do + case type do + {:varchar, size} -> + {:varchar, size, nil, nil} + + {:binary, size} -> + {:binary, size, nil, nil} + + {:decimal, precision, scale} -> + {:decimal, nil, precision, scale} + + {:decimal, precision} -> + {:decimal, nil, precision, nil} + + {other, size} when is_atom(other) and is_integer(size) -> + {other, size, nil, nil} + + {:array, other} -> + {nested, size, precision, scale} = migration_type_to_type(other) + {{:array, nested}, size, precision, scale} + + other -> + {other, nil, nil, nil} + end + end + defp find_reference(resource, table, attribute) do resource |> Ash.Resource.Info.relationships() @@ -3581,22 +3589,6 @@ defmodule AshPostgres.MigrationGenerator do ["array", sanitize_type(type, size, scale, precision)] end - defp sanitize_type(:varchar, size, _scale, _precision) when not is_nil(size) do - ["varchar", size] - end - - defp sanitize_type(:binary, size, _scale, _precision) when not is_nil(size) do - ["binary", size] - end - - defp sanitize_type(:decimal, _size, scale, precision) do - ["decimal", precision, scale] - end - - defp sanitize_type(type, size, precision, decimal) when is_tuple(type) do - sanitize_type(elem(type, 0), size, precision, decimal) - end - defp sanitize_type(type, _, _, _) do type end @@ -3686,27 +3678,6 @@ defmodule AshPostgres.MigrationGenerator do defp load_attribute(attribute, table) do type = load_type(attribute.type) - {type, size, scale, precision} = - case type do - {:varchar, size} -> - {:varchar, size, nil, nil} - - {:binary, size} -> - {:binary, size, nil, nil} - - {other, size} when is_atom(other) and is_integer(size) -> - {other, size, nil, nil} - - {:decimal, precision} -> - {:decimal, nil, nil, precision} - - {:decimal, precision, scale} -> - {:decimal, nil, precision, scale} - - other -> - {other, nil, nil, nil} - end - attribute = if Map.has_key?(attribute, :name) do Map.put(attribute, :source, maybe_to_atom(attribute.name)) @@ -3716,9 +3687,9 @@ defmodule AshPostgres.MigrationGenerator do attribute |> Map.put(:type, type) - |> Map.put(:size, size) - |> Map.put(:precision, precision) - |> Map.put(:scale, scale) + |> Map.put_new(:size, nil) + |> Map.put_new(:precision, nil) + |> Map.put_new(:scale, nil) |> Map.put_new(:default, "nil") |> Map.update!(:default, &(&1 || "nil")) |> Map.update!(:references, fn @@ -3797,25 +3768,7 @@ defmodule AshPostgres.MigrationGenerator do {:array, load_type(type)} end - defp load_type(["varchar", size]) do - {:varchar, size} - end - - defp load_type(["binary", size]) do - {:binary, size} - end - - defp load_type(["decimal", precision]) do - {:decimal, precision} - end - - defp load_type(["decimal", precision, scale]) do - {:decimal, precision, scale} - end - - defp load_type([string, size]) when is_binary(string) and is_integer(size) do - {String.to_existing_atom(string), size} - end + defp load_type([type | _]), do: String.to_existing_atom(type) defp load_type(type) do maybe_to_atom(type) From c653161950948a7a39be8e699f72efefc99fdea9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 10 Jun 2025 19:22:22 -0400 Subject: [PATCH 1091/1215] chore: release version v2.6.6 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d810f81..cfda92aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.6](https://github.com/ash-project/ash_postgres/compare/v2.6.5...v2.6.6) (2025-06-10) + + + + +### Bug Fixes: + +* simply storage of size/scale/precision information + ## [v2.6.5](https://github.com/ash-project/ash_postgres/compare/v2.6.4...v2.6.5) (2025-06-10) diff --git a/mix.exs b/mix.exs index d1548b95..cf243ab3 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.5" + @version "2.6.6" def project do [ From a5b590b59d43be5cfc4394c9ad4349064792bbf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:26:49 -0400 Subject: [PATCH 1092/1215] chore(deps): bump the production-dependencies group with 4 updates (#566) Bumps the production-dependencies group with 4 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql), [ecto](https://github.com/elixir-ecto/ecto) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.5.15 to 3.5.18 - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.15...v3.5.18) Updates `ash_sql` from 0.2.78 to 0.2.79 - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.78...v0.2.79) Updates `ecto` from 3.12.5 to 3.12.6 - [Release notes](https://github.com/elixir-ecto/ecto/releases) - [Changelog](https://github.com/elixir-ecto/ecto/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto/compare/v3.12.5...v3.12.6) Updates `igniter` from 0.6.6 to 0.6.7 - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.6.6...v0.6.7) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-version: 0.2.79 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ecto dependency-version: 3.12.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-version: 0.6.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 0fd35a2e..24d983d4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.15", "418055e74c4d85d1f397577407aa71c29d44ef0b729be144e23e64630ad2eedc", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.61 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8ee885ccc6be65d1023cccbdfda6a534497cf5170d53b67c0b29db72c0c30b9b"}, - "ash_sql": {:hex, :ash_sql, "0.2.78", "130d54ca85b8fbd1e65f038beafb905ade1941d1f5e3aef1fec7f8bba7414fc2", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a640ec0ef176f3a0e95f88d8639d9d23479ca8a5f5e7e177af351f18cddf4f30"}, + "ash": {:hex, :ash, "3.5.18", "3fafe7b0c67fb863409ea06fd9ca0f1cb681b4e798973989aabedc42a82e8f2a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "52e3156f832026aeef0ca4a705741d29d5a94a7a393ab280b694a2d3d43bcf12"}, + "ash_sql": {:hex, :ash_sql, "0.2.79", "1378bb33a7469f50e2854b7490add380ad2cbf73dda9d5b373194097ad03d3eb", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ef10d909f238912b4be7aeb587ae32cce7e42d136bf94da07bc7e54a475cdde"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -9,7 +9,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto": {:hex, :ecto, "3.12.6", "8bf762dc5b87d85b7aca7ad5fe31ef8142a84cea473a3381eb933bd925751300", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c0cba01795463eebbcd9e4b5ef53c1ee8e68b9c482baef2a80de5a61e7a57fe"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.6", "82d707a2419a95e6ea115949c68a9113dfc0b4802d3d8386aa351feb0ead71ee", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a85832987fc78f5fdc38f628a62acfd50b4e441166496fea15c7b05218fa84f5"}, + "igniter": {:hex, :igniter, "0.6.7", "4e183afc59d89289e223c4282fd3e9bb39b82e28d0aa6d3369f70fbd3e21a243", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "43b0a584dc84fd1320772c87047355b604ed2bcdd25392b17f7da8bdd09b61ac"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -44,7 +44,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.63", "bc771c3c1028136559507e235e721e730118ca316e22e75da347ae1c195b94a1", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "42708ab2884af6abc1aa33aa981a4f51f65146d7879651d1e6c11948a4d8fcfe"}, + "spark": {:hex, :spark, "2.2.65", "4c10d109c108417ce394158f330be09ef184878bde45de6462397fbda68cec29", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d66d5070a77f4c69cb4f007e941ac17d5d751ce71190fcd6e6e5fb42ba86f101"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 4a94b817ffc850421d485ff0e41b37b98e43abeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:27:03 -0400 Subject: [PATCH 1093/1215] chore(deps-dev): bump the dev-dependencies group with 2 updates (#567) Bumps the dev-dependencies group with 2 updates: [git_ops](https://github.com/zachdaniel/git_ops) and [mix_audit](https://github.com/mirego/mix_audit). Updates `git_ops` from 2.7.3 to 2.8.0 - [Changelog](https://github.com/zachdaniel/git_ops/blob/master/CHANGELOG.md) - [Commits](https://github.com/zachdaniel/git_ops/compare/v2.7.3...v2.8.0) Updates `mix_audit` from 2.1.4 to 2.1.5 - [Changelog](https://github.com/mirego/mix_audit/blob/main/CHANGELOG.md) - [Commits](https://github.com/mirego/mix_audit/compare/v2.1.4...v2.1.5) --- updated-dependencies: - dependency-name: git_ops dependency-version: 2.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: mix_audit dependency-version: 2.1.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 24d983d4..d7a02173 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.7.3", "c993aedb11005752e321d482de6f2a46d0b5d5f09ce69961f31a856e76bf4f12", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "c54ee65e12778be1f4dd6a0921e57ab2bddd35bd6130cbe274dcb1f0a21ca59d"}, + "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "igniter": {:hex, :igniter, "0.6.7", "4e183afc59d89289e223c4282fd3e9bb39b82e28d0aa6d3369f70fbd3e21a243", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "43b0a584dc84fd1320772c87047355b604ed2bcdd25392b17f7da8bdd09b61ac"}, @@ -32,7 +32,7 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, + "mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, From 61a075f46a96ddbf9d94137bbd20ffd5db7e6d11 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Jun 2025 08:47:43 -0400 Subject: [PATCH 1094/1215] test: update tests --- .../test_repo/posts/20250612113920.json | 678 ++++++++++++++++++ .../20250612113920_migrate_resources55.exs | 21 + test/filter_test.exs | 15 + test/support/resources/post.ex | 1 - 4 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/posts/20250612113920.json create mode 100644 priv/test_repo/migrations/20250612113920_migrate_resources55.exs diff --git a/priv/resource_snapshots/test_repo/posts/20250612113920.json b/priv/resource_snapshots/test_repo/posts/20250612113920.json new file mode 100644 index 00000000..4d585717 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250612113920.json @@ -0,0 +1,678 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "metadata", + "type": "map" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "string_point", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "person_detail", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "model", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "scale": null, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "scale": null, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "scale": null, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "points" + }, + "scale": null, + "size": null, + "source": "db_point_id", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_string_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "string_points" + }, + "scale": null, + "size": null, + "source": "db_string_point_id", + "type": "text" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "90D4DD5BCCA4A66F2979715F980190DBC33A0A71C697943F2756B87302E0FE5A", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250612113920_migrate_resources55.exs b/priv/test_repo/migrations/20250612113920_migrate_resources55.exs new file mode 100644 index 00000000..082dad95 --- /dev/null +++ b/priv/test_repo/migrations/20250612113920_migrate_resources55.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources55 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(:posts) do + modify(:model, :map, null: true) + end + end + + def down do + alter table(:posts) do + modify(:model, :map, null: false) + end + end +end diff --git a/test/filter_test.exs b/test/filter_test.exs index 83bcbba0..261dd0bf 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -21,6 +21,21 @@ defmodule AshPostgres.FilterTest do end end + describe "type casting" do + test "it does not do unnecessary type casting" do + {query, vars} = + Post + |> Ash.Query.filter(version == ^10) + |> Ash.data_layer_query!() + |> Map.get(:query) + |> then(&AshPostgres.TestRepo.to_sql(:all, &1)) + + assert vars == ["sponsored", 10] + + assert String.contains?(query, "(p0.\"version\"::bigint = $2::bigint)") + end + end + describe "ci_string argument casting" do test "it properly casts" do Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 369283bc..a2c3ceea 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -551,7 +551,6 @@ defmodule AshPostgres.Test.Post do ] ) - allow_nil?(false) default(fn -> {3.0, 3.0, 1.0} end) end From fd2b80d3173e338662b3a2bfed91c3fb60a290ca Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Jun 2025 08:47:54 -0400 Subject: [PATCH 1095/1215] chore: update bench --- benchmarks/bulk_create.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs index 5d9ee395..99f235a1 100644 --- a/benchmarks/bulk_create.exs +++ b/benchmarks/bulk_create.exs @@ -1,5 +1,7 @@ alias AshPostgres.Test.{Domain, Post} +AshPostgres.TestRepo.start_link() + ten_rows = 1..10 |> Enum.map(fn i -> From aff1c410f51a12a5ea6e50a7be0e33dbc3658e89 Mon Sep 17 00:00:00 2001 From: Christian Alexander Date: Thu, 12 Jun 2025 08:09:14 -0700 Subject: [PATCH 1096/1215] docs: Fix mix task documentation typos (#568) --- lib/mix/tasks/ash_postgres.drop.ex | 2 +- lib/mix/tasks/ash_postgres.gen.resources.ex | 4 ++-- lib/mix/tasks/ash_postgres.generate_migrations.ex | 2 +- lib/mix/tasks/ash_postgres.migrate.ex | 4 ++-- lib/mix/tasks/ash_postgres.rollback.ex | 2 +- lib/mix/tasks/ash_postgres.squash_snapshots.ex | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 43028c38..e54ffddb 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -39,7 +39,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do * `--force-drop` - force the database to be dropped even if it has connections to it (requires PostgreSQL 13+) * `--no-compile` - do not compile before dropping - * `--no-deps-check` - do not compile before dropping + * `--no-deps-check` - do not check dependencies before dropping """ @doc false diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index 2196c2f0..a187f8d9 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -2,7 +2,7 @@ if Code.ensure_loaded?(Igniter) do defmodule Mix.Tasks.AshPostgres.Gen.Resources do use Igniter.Mix.Task - @example "mix ash_postgres.gen.resource MyApp.MyDomain" + @example "mix ash_postgres.gen.resources MyApp.MyDomain" @shortdoc "Generates resources based on a database schema" @@ -23,7 +23,7 @@ if Code.ensure_loaded?(Igniter) do - `repo`, `r` - The repo or repos to generate resources for, comma separated. Can be specified multiple times. Defaults to all repos. - `tables`, `t` - Defaults to `public.*`. The tables to generate resources for, comma separated. Can be specified multiple times. See the section on tables for more. - `skip-tables`, `s` - The tables to skip generating resources for, comma separated. Can be specified multiple times. See the section on tables for more. `schema_migrations` is always skipped. - - `snapshots-only` - Only generate snapshots for the generated resources, and not migraitons. + - `snapshots-only` - Only generate snapshots for the generated resources, and not migrations. - `extend`, `e` - Extension or extensions to apply to the generated resources. See `mix ash.patch.extend` for more. - `yes`, `y` - Answer yes (or skip) to all questions. - `default-actions` - Add default actions for each resource. Defaults to `true`. diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 8431572c..028ac8bb 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -37,7 +37,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do Generally speaking, it is bad practice to drop columns when you deploy a change that would remove an attribute. The main reasons for this are backwards compatibility and rolling restarts. - If you deploy an attribute removal, and run migrations. Regardless of your deployment sstrategy, you + If you deploy an attribute removal, and run migrations. Regardless of your deployment strategy, you won't be able to roll back, because the data has been deleted. In a rolling restart situation, some of the machines/pods/whatever may still be running after the column has been deleted, causing errors. With this in mind, its best not to delete those columns until later, after the data has been confirmed unnecessary. diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index f8ab1d20..7da14a78 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do import AshPostgres.Mix.Helpers, only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] - @shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) domains" + @shortdoc "Runs the repository migrations for all repositories in the provided (or configured) domains" @aliases [ n: :step, @@ -95,7 +95,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do * `--no-compile` - does not compile applications before migrating - * `--no-deps-check` - does not check depedendencies before migrating + * `--no-deps-check` - does not check dependencies before migrating * `--migrations-path` - the path to load the migrations from, defaults to `"priv/repo/migrations"`. This option may be given multiple times in which case the migrations diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index add81d59..d59fc1bf 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do mix ash_postgres.rollback --to 20080906120000 ## Command line options - * `--domains` - the domains who's repos should be rolledback + * `--domains` - the domains whose repos should be rolled back * `--all` - revert all applied migrations * `--repo`, `-r` - the repo to rollback * `--step` / `-n` - revert n number of applied migrations diff --git a/lib/mix/tasks/ash_postgres.squash_snapshots.ex b/lib/mix/tasks/ash_postgres.squash_snapshots.ex index 1cf1fd68..84032c9b 100644 --- a/lib/mix/tasks/ash_postgres.squash_snapshots.ex +++ b/lib/mix/tasks/ash_postgres.squash_snapshots.ex @@ -28,7 +28,7 @@ defmodule Mix.Tasks.AshPostgres.SquashSnapshots do a remaining snapshot. `last` keeps the name of the last snapshot, `first` renames it to the previously first, `zero` sets name with fourteen zeros. * `--snapshot-path` - a custom path to stored snapshots. The default is "priv/resource_snapshots". - * `--quiet` - no messages will not be printed. + * `--quiet` - no messages will be printed. * `--dry-run` - no files are touched, instead prints folders that have snapshots to squash. * `--check` - no files are touched, instead returns an exit(1) code if there are snapshots to squash. """ From 25c924d43893c6a4ff78ed11dd281cc7054cb4b1 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Fri, 13 Jun 2025 02:32:24 +0200 Subject: [PATCH 1097/1215] fix: double select error (#569) --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 10d1a17b..ffa72385 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3423,7 +3423,7 @@ defmodule AshPostgres.DataLayer do ) query_with_select = - from(sub in query, + from(sub in Ecto.Query.exclude(query, :select), join: row in ^query.__ash_bindings__.resource, # why doesn't `.root_binding` work the way I expect it to here? on: ^dynamic, From c3304a13f42a072e3afc126ada37c03009409f2c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Jun 2025 20:33:06 -0400 Subject: [PATCH 1098/1215] chore: credo --- test/support/complex_calculations/resources/documentation.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex index df6c5415..405c9dae 100644 --- a/test/support/complex_calculations/resources/documentation.ex +++ b/test/support/complex_calculations/resources/documentation.ex @@ -61,6 +61,7 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do end defmodule AshPostgres.Test.TimezoneHelper do + @moduledoc false def seoul_time do # Fixed datetime for testing - equivalent to 2024-05-01 21:00:00 in Seoul (UTC+9) ~U[2024-05-01 12:00:00Z] |> DateTime.shift_zone!("Asia/Seoul") From d2f5d17db06091e04980def178a55c75f53c4893 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 12 Jun 2025 20:34:02 -0400 Subject: [PATCH 1099/1215] chore: release version v2.6.7 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfda92aa..c9ac6016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.7](https://github.com/ash-project/ash_postgres/compare/v2.6.6...v2.6.7) (2025-06-13) + + + + +### Bug Fixes: + +* double select error (#569) by Barnabas Jovanovics + ## [v2.6.6](https://github.com/ash-project/ash_postgres/compare/v2.6.5...v2.6.6) (2025-06-10) diff --git a/mix.exs b/mix.exs index cf243ab3..6f5a16cf 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.6" + @version "2.6.7" def project do [ From cb93a8f95be1bbf21b8fc23c5d44552b3521936f Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Wed, 18 Jun 2025 01:06:45 +0800 Subject: [PATCH 1100/1215] docs: Add minimum supported PostgreSQL version to README (#570) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 51f74e61..1bef9edc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Welcome! `AshPostgres` is the PostgreSQL data layer for [Ash Framework](https://hexdocs.pm/ash). +Minimum required PostgreSQL version: `13.0` + ## Tutorials - [Get Started](documentation/tutorials/get-started-with-ash-postgres.md) From f9d13294c29f786c95e7f55cfb627bad13deae3d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 17 Jun 2025 14:51:49 -0400 Subject: [PATCH 1101/1215] fix: ensure prefix is set even with create_schemas_in_migrations? false --- lib/migration_generator/phase.ex | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/migration_generator/phase.ex b/lib/migration_generator/phase.ex index 6be7dd81..a4e5861b 100644 --- a/lib/migration_generator/phase.ex +++ b/lib/migration_generator/phase.ex @@ -19,12 +19,18 @@ defmodule AshPostgres.MigrationGenerator.Phase do Enum.map_join(operations, "\n", fn operation -> operation.__struct__.up(operation) end) <> "\nend" else - {pre_create, opts} = + pre_create = if schema && repo.create_schemas_in_migrations?() do - {"execute(\"CREATE SCHEMA IF NOT EXISTS #{schema}\")" <> "\n\n", - ", prefix: \"#{schema}\""} + "execute(\"CREATE SCHEMA IF NOT EXISTS #{schema}\")" <> "\n\n" else - {"", ""} + "" + end + + opts = + if schema do + ", prefix: \"#{schema}\"" + else + "" end pre_create <> From 46f325e6e2902fcb3a7737f79d16568aaacba2e5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 17 Jun 2025 15:02:32 -0400 Subject: [PATCH 1102/1215] chore: update deps --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index d7a02173..69d19586 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.18", "3fafe7b0c67fb863409ea06fd9ca0f1cb681b4e798973989aabedc42a82e8f2a", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "52e3156f832026aeef0ca4a705741d29d5a94a7a393ab280b694a2d3d43bcf12"}, - "ash_sql": {:hex, :ash_sql, "0.2.79", "1378bb33a7469f50e2854b7490add380ad2cbf73dda9d5b373194097ad03d3eb", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ef10d909f238912b4be7aeb587ae32cce7e42d136bf94da07bc7e54a475cdde"}, + "ash": {:hex, :ash, "3.5.21", "389303c193962d67fd59da18a3557f5015fdfdaeddaa77150db539bc7203d1a1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cb90005d1972e22d0d2ae514394e43e0d67cce18c4485595aa3d3e4bbf25260f"}, + "ash_sql": {:hex, :ash_sql, "0.2.81", "cdc7767400425bb11928ba76e0cdc66a82ff9eabea59af21eb474a60732815ce", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c2ccef696620852c93cc3fb3bc564200b910a3487266f54b51c444a7e5dfa3d0"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From db2d60d8c2196545680724d5190d6ff587d7f68a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 17 Jun 2025 21:34:41 -0400 Subject: [PATCH 1103/1215] test: add tests for new boolean expressions --- .../test_repo/posts/20250618011917.json | 690 ++++++++++++++++++ .../20250618011917_migrate_resources56.exs | 21 + test/filter_test.exs | 33 + test/support/resources/post.ex | 1 + 4 files changed, 745 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/posts/20250618011917.json create mode 100644 priv/test_repo/migrations/20250618011917_migrate_resources56.exs diff --git a/priv/resource_snapshots/test_repo/posts/20250618011917.json b/priv/resource_snapshots/test_repo/posts/20250618011917.json new file mode 100644 index 00000000..5031b3cf --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250618011917.json @@ -0,0 +1,690 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "1", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "version", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "title_column", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "not_selected_by_default", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "datetime", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "limited_score", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "public", + "type": "boolean" + }, + { + "allow_nil?": false, + "default": "true", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "is_special", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "category", + "type": "citext" + }, + { + "allow_nil?": true, + "default": "\"sponsored\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "type", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "price", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "\"0\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "decimal", + "type": "decimal" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "status_enum", + "type": "status" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "metadata", + "type": "map" + }, + { + "allow_nil?": false, + "default": "2", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "constrained_int", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "point", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "composite_point", + "type": "custom_point" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "string_point", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "person_detail", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "stuff", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "list_of_stuff", + "type": [ + "array", + "map" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_custom_one", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_custom_two", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_on_upper", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uniq_if_contains_foo", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "model", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "list_containing_nils", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "ltree_unescaped", + "type": "ltree" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "ltree_escaped", + "type": "ltree" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "created_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "updated_at", + "type": "timestamptz(6)" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_organization_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "orgs" + }, + "scale": null, + "size": null, + "source": "organization_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_parent_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "scale": null, + "size": null, + "source": "parent_post_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_author_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "authors" + }, + "scale": null, + "size": null, + "source": "author_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "points" + }, + "scale": null, + "size": null, + "source": "db_point_id", + "type": [ + "array", + "float" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "posts_db_string_point_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "string_points" + }, + "scale": null, + "size": null, + "source": "db_string_point_id", + "type": "text" + } + ], + "base_filter": "type = 'sponsored'", + "check_constraints": [ + { + "attribute": [ + "price" + ], + "base_filter": "type = 'sponsored'", + "check": "price > 0", + "name": "price_must_be_positive" + } + ], + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": true, + "error_fields": [ + "uniq_custom_one", + "uniq_custom_two" + ], + "fields": [ + { + "type": "atom", + "value": "uniq_custom_one" + }, + { + "type": "atom", + "value": "uniq_custom_two" + } + ], + "include": null, + "message": "dude what the heck", + "name": null, + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": true, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "28B63D8AA18EE499B2A10DFF793995FF00B811F6CB45E01EBFEF15516D305DF8", + "identities": [ + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_if_contains_foo_index", + "keys": [ + { + "type": "atom", + "value": "uniq_if_contains_foo" + } + ], + "name": "uniq_if_contains_foo", + "nils_distinct?": true, + "where": "(uniq_if_contains_foo LIKE '%foo%')" + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_on_upper_index", + "keys": [ + { + "type": "string", + "value": "(UPPER(uniq_on_upper))" + } + ], + "name": "uniq_on_upper", + "nils_distinct?": true, + "where": null + }, + { + "all_tenants?": false, + "base_filter": "type = 'sponsored'", + "index_name": "posts_uniq_one_and_two_index", + "keys": [ + { + "type": "atom", + "value": "uniq_one" + }, + { + "type": "atom", + "value": "uniq_two" + } + ], + "name": "uniq_one_and_two", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "posts" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250618011917_migrate_resources56.exs b/priv/test_repo/migrations/20250618011917_migrate_resources56.exs new file mode 100644 index 00000000..641aa008 --- /dev/null +++ b/priv/test_repo/migrations/20250618011917_migrate_resources56.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources56 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(:posts) do + add(:is_special, :boolean, null: false, default: true) + end + end + + def down do + alter table(:posts) do + remove(:is_special) + end + end +end diff --git a/test/filter_test.exs b/test/filter_test.exs index 261dd0bf..d0332301 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -34,6 +34,39 @@ defmodule AshPostgres.FilterTest do assert String.contains?(query, "(p0.\"version\"::bigint = $2::bigint)") end + + test "it uses coalesce to optimize the || operator for non-booleans" do + {query, _vars} = + Post + |> Ash.Query.filter((version || 10) == 20) + |> Ash.data_layer_query!() + |> Map.get(:query) + |> then(&AshPostgres.TestRepo.to_sql(:all, &1)) + + assert String.contains?(query, "(coalesce(p0.\"version\"::bigint, $2::bigint)") + end + + test "it uses OR to optimize the || operator for booleans" do + {query, _vars} = + Post + |> Ash.Query.filter(is_special || true) + |> Ash.data_layer_query!() + |> Map.get(:query) + |> then(&AshPostgres.TestRepo.to_sql(:all, &1)) + + assert String.contains?(query, "(p0.\"is_special\"::boolean OR $2::boolean)") + end + + test "it uses AND to optimize the && operator for booleans" do + {query, _vars} = + Post + |> Ash.Query.filter(is_special && public) + |> Ash.data_layer_query!() + |> Map.get(:query) + |> then(&AshPostgres.TestRepo.to_sql(:all, &1)) + + assert String.contains?(query, "(p0.\"is_special\"::boolean AND p0.\"public\"::boolean)") + end end describe "ci_string argument casting" do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index a2c3ceea..f22d3fef 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -509,6 +509,7 @@ defmodule AshPostgres.Test.Post do attribute(:limited_score, :integer, public?: true, constraints: [min: 0, max: 100]) attribute(:public, :boolean, public?: true) + attribute(:is_special, :boolean, public?: true, allow_nil?: false, default: true) attribute(:category, CiCategory, public?: true) attribute(:type, :atom, default: :sponsored, writable?: false, public?: false) attribute(:price, :integer, public?: true) From 0994310ef1c608d9609509e9d36948ca801ddc56 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Jun 2025 14:32:29 -0400 Subject: [PATCH 1104/1215] chore: more literal -> identifier --- documentation/topics/advanced/manual-relationships.md | 2 +- lib/data_layer.ex | 6 +++--- lib/sql_implementation.ex | 2 +- mix.exs | 4 ++-- mix.lock | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index 2da54328..a7adaac3 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -240,7 +240,7 @@ defmodule MyApp.Employee.ManagedEmployees do employee_keys = Employee.__schema__(:fields) cte_name_ref = - from(cte in fragment("?", literal(^cte_name)), select: map(cte, ^employee_keys)) + from(cte in fragment("?", identifier(^cte_name)), select: map(cte, ^employee_keys)) recursion_query = query diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ffa72385..ddfdc060 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2109,7 +2109,7 @@ defmodule AshPostgres.DataLayer do |> Enum.map(fn upsert_field -> # for safety, we check once more at the end that all values in # upsert_fields are names of attributes. This is because - # below we use `literal/1` to bring them into the query + # below we use `identifier/1` to bring them into the query if is_nil(resource.__schema__(:type, upsert_field)) do raise "Only attribute names can be used in upsert_fields" end @@ -2122,7 +2122,7 @@ defmodule AshPostgres.DataLayer do [], fragment( "COALESCE(EXCLUDED.?, ?)", - literal(^to_string(get_source_for_upsert_field(upsert_field, resource))), + identifier(^to_string(get_source_for_upsert_field(upsert_field, resource))), ^default ) )} @@ -2136,7 +2136,7 @@ defmodule AshPostgres.DataLayer do [], fragment( "EXCLUDED.?", - literal(^to_string(get_source_for_upsert_field(upsert_field, resource))) + identifier(^to_string(get_source_for_upsert_field(upsert_field, resource))) ) )} end diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index a91b8786..99d80f8f 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -56,7 +56,7 @@ defmodule AshPostgres.SqlImplementation do [], fragment( "EXCLUDED.?", - literal( + identifier( ^to_string( AshPostgres.DataLayer.get_source_for_upsert_field( attribute, diff --git a/mix.exs b/mix.exs index 6f5a16cf..a4db2bc6 100644 --- a/mix.exs +++ b/mix.exs @@ -169,8 +169,8 @@ defmodule AshPostgres.MixProject do {:ash, ash_version("~> 3.5 and >= 3.5.13")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, {:igniter, "~> 0.6", optional: true}, - {:ecto_sql, "~> 3.12"}, - {:ecto, "~> 3.12 and >= 3.12.1"}, + {:ecto_sql, "~> 3.13"}, + {:ecto, "~> 3.13"}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, # dev/test dependencies diff --git a/mix.lock b/mix.lock index 69d19586..693c305b 100644 --- a/mix.lock +++ b/mix.lock @@ -9,9 +9,9 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.12.6", "8bf762dc5b87d85b7aca7ad5fe31ef8142a84cea473a3381eb933bd925751300", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c0cba01795463eebbcd9e4b5ef53c1ee8e68b9c482baef2a80de5a61e7a57fe"}, + "ecto": {:hex, :ecto, "3.13.0", "7528ef4f3a4cdcfebeb7eb6545806c8109529b385a69f701fc3d77b5b8bde6e7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "061f095f1cc097f71f743b500affc792d6869df22b1946a73ab5495eb9b4a280"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, - "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "ecto_sql": {:hex, :ecto_sql, "3.13.0", "a732428f38ce86612a2c34a1ea5d0a9642a5a71f044052007fd2f2e815707990", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ce13085122a0871d93ea9ba1a886447d89c07f3b563e19e0b3dcdf201ed9fe9"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, From ebc3101400e764f1b83c0020c9615a0264de365c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Jun 2025 19:49:53 -0400 Subject: [PATCH 1105/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 693c305b..6b5900db 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.21", "389303c193962d67fd59da18a3557f5015fdfdaeddaa77150db539bc7203d1a1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cb90005d1972e22d0d2ae514394e43e0d67cce18c4485595aa3d3e4bbf25260f"}, - "ash_sql": {:hex, :ash_sql, "0.2.81", "cdc7767400425bb11928ba76e0cdc66a82ff9eabea59af21eb474a60732815ce", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c2ccef696620852c93cc3fb3bc564200b910a3487266f54b51c444a7e5dfa3d0"}, + "ash_sql": {:hex, :ash_sql, "0.2.82", "a4fe01ccd2c29ce43af50233e63cd1298735b68ee2c22a1cbb0baa5f31f78ab5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a31f1065b72387b7a19d4e357f06904910b4c4fc8986209975786015f40cf795"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -38,13 +38,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.15.4", "ef0c56a901c132529a14ab59fed0ccb4fcecb24308fb189a94c908255d4fdafc", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "783bf62fd0c72ded033afabdb8b6190b7048769771a2a97256e6f0bf4fb0a891"}, + "reactor": {:hex, :reactor, "0.15.5", "341d9ee664d6141df6639f227692ee6adc8a493d04232dee79e8a4a88e6cef8a", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f9f440ecbdb0c41a832902a692608bd24be621fa7a602819d0dd12971d69f9aa"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.65", "4c10d109c108417ce394158f330be09ef184878bde45de6462397fbda68cec29", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d66d5070a77f4c69cb4f007e941ac17d5d751ce71190fcd6e6e5fb42ba86f101"}, + "spark": {:hex, :spark, "2.2.66", "b7b47e76961c747f6128ad092c2109dbf742342dec533d3002c35207cb5f6b8e", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "8f0b79839033ab816c2ae37aea29ded83e245a74240e2c931e2396531a9760d6"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From abaa6e71ef364e7562e8460ca929d3618a7887ac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 18 Jun 2025 19:50:31 -0400 Subject: [PATCH 1106/1215] chore: release version v2.6.8 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ac6016..4600e4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.8](https://github.com/ash-project/ash_postgres/compare/v2.6.7...v2.6.8) (2025-06-18) + + + + +### Bug Fixes: + +* ensure prefix is set even with create_schemas_in_migrations? false by Zach Daniel + ## [v2.6.7](https://github.com/ash-project/ash_postgres/compare/v2.6.6...v2.6.7) (2025-06-13) diff --git a/mix.exs b/mix.exs index a4db2bc6..af70fd8a 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.7" + @version "2.6.8" def project do [ From adf7a0420d7aef3fb60bc46dfe8955eec20d969a Mon Sep 17 00:00:00 2001 From: Oliver Severin Mulelid-Tynes Date: Thu, 19 Jun 2025 18:42:59 +0200 Subject: [PATCH 1107/1215] fix: Fix foreign key constraint on specially named references (#572) --- lib/data_layer.ex | 9 ++++++++- test/references_test.exs | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index ddfdc060..22efd25e 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2682,8 +2682,15 @@ defmodule AshPostgres.DataLayer do resource |> Ash.Resource.Info.relationships() |> Enum.reduce(changeset, fn relationship, changeset -> + # Check if there's a custom reference name defined in the DSL name = - "#{AshPostgres.DataLayer.Info.table(resource)}_#{relationship.source_attribute}_fkey" + case AshPostgres.DataLayer.Info.reference(resource, relationship.name) do + %{name: custom_name} when not is_nil(custom_name) -> + custom_name + + _ -> + "#{AshPostgres.DataLayer.Info.table(resource)}_#{relationship.source_attribute}_fkey" + end case repo.default_constraint_match_type(:foreign, name) do {:regex, regex} -> diff --git a/test/references_test.exs b/test/references_test.exs index afb89acd..626a2d97 100644 --- a/test/references_test.exs +++ b/test/references_test.exs @@ -105,4 +105,27 @@ defmodule AshPostgres.ReferencesTest do end end end + + test "named reference results in properly applied foreign_key_constraint/3 on the underlying changeset" do + # Create a comment with an invalid post_id + assert {:error, %Ash.Error.Invalid{errors: errors}} = + AshPostgres.Test.Comment + |> Ash.Changeset.for_create(:create, %{ + title: "Test Comment", + # This post doesn't exist + post_id: Ash.UUID.generate() + }) + |> Ash.create() + + assert [ + %Ash.Error.Changes.InvalidAttribute{ + field: :post_id, + message: "does not exist", + private_vars: private_vars + } + ] = errors + + assert Keyword.get(private_vars, :constraint) == "special_name_fkey" + assert Keyword.get(private_vars, :constraint_type) == :foreign_key + end end From 919b9dfbbdfc0b89b776a527af986a9d604622cd Mon Sep 17 00:00:00 2001 From: Marc Planelles Date: Sat, 21 Jun 2025 23:42:24 +0200 Subject: [PATCH 1108/1215] fix: smallserial not mapping to proper type (#574) --- lib/resource_generator/spec.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 38eebac5..ff942f16 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -958,7 +958,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do defp type("numeric"), do: {:ok, :decimal} defp type("decimal"), do: {:ok, :decimal} defp type("smallint"), do: {:ok, :integer} - defp type("smallserial"), do: {:ok, :ineger} + defp type("smallserial"), do: {:ok, :integer} defp type("serial"), do: {:ok, :integer} defp type("text"), do: {:ok, :string} defp type("time"), do: {:ok, :time} From d6c35209fb124d4b5c605135262780acd7dd9eb6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 22 Jun 2025 16:44:38 -0400 Subject: [PATCH 1109/1215] test: add tests for calculation typing --- test/calculation_test.exs | 11 +++++++++++ test/support/resources/post.ex | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 6aa911fe..287ccf9a 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1034,4 +1034,15 @@ defmodule AshPostgres.CalculationTest do assert full_name == "name" end + + test "calculation with fragment and cond returning integer doesn't cause Postgrex encoding error" do + Post + |> Ash.Changeset.for_create(:create, %{title: "hello ash lovers"}) + |> Ash.create!() + + assert [%Post{}] = + Post + |> Ash.Query.sort("posts_with_matching_title.relevance_score") + |> Ash.read!() + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index f22d3fef..85cd60bb 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -788,6 +788,24 @@ defmodule AshPostgres.Test.Post do end calculations do + calculate :relevance_score, + :integer, + expr( + cond do + fragment( + "ts_rank_cd(to_tsvector(?), ?, 32)::float", + ^ref(:title), + fragment("to_tsquery(?)", ^arg(:query)) + ) > 0.6 -> + 1 + + true -> + 2 + end + ) do + argument(:query, :string) + end + calculate(:upper_thing, :string, expr(fragment("UPPER(?)", uniq_on_upper))) calculate(:upper_title, :string, expr(fragment("UPPER(?)", title))) From 2ff3cb2f44486b07a12ca28bbbe76a1542cc30d6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Jun 2025 23:54:12 -0400 Subject: [PATCH 1110/1215] chore: credo --- mix.lock | 12 ++++++------ test/support/resources/post.ex | 18 ++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/mix.lock b/mix.lock index 6b5900db..601ad73d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,17 +1,17 @@ %{ - "ash": {:hex, :ash, "3.5.21", "389303c193962d67fd59da18a3557f5015fdfdaeddaa77150db539bc7203d1a1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cb90005d1972e22d0d2ae514394e43e0d67cce18c4485595aa3d3e4bbf25260f"}, + "ash": {:hex, :ash, "3.5.23", "e3c4e508569850fc276780f4ac94bee4c5f2d8e259ec2157c71e8e286db1c3ce", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd491cae52fa73b150e2c3b095ec3ae3aef681666503c300cee60c886d94ebfd"}, "ash_sql": {:hex, :ash_sql, "0.2.82", "a4fe01ccd2c29ce43af50233e63cd1298735b68ee2c22a1cbb0baa5f31f78ab5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a31f1065b72387b7a19d4e357f06904910b4c4fc8986209975786015f40cf795"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, - "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.0", "7528ef4f3a4cdcfebeb7eb6545806c8109529b385a69f701fc3d77b5b8bde6e7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "061f095f1cc097f71f743b500affc792d6869df22b1946a73ab5495eb9b4a280"}, + "ecto": {:hex, :ecto, "3.13.2", "7d0c0863f3fc8d71d17fc3ad3b9424beae13f02712ad84191a826c7169484f01", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "669d9291370513ff56e7b7e7081b7af3283d02e046cf3d403053c557894a0b3e"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, - "ecto_sql": {:hex, :ecto_sql, "3.13.0", "a732428f38ce86612a2c34a1ea5d0a9642a5a71f044052007fd2f2e815707990", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ce13085122a0871d93ea9ba1a886447d89c07f3b563e19e0b3dcdf201ed9fe9"}, + "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.7", "4e183afc59d89289e223c4282fd3e9bb39b82e28d0aa6d3369f70fbd3e21a243", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "43b0a584dc84fd1320772c87047355b604ed2bcdd25392b17f7da8bdd09b61ac"}, + "igniter": {:hex, :igniter, "0.6.8", "f058e7e5e3e69af9c795cc3022a92f802c8e2e1fd366579f6b60af328f69e2bb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b0d9cf65a64ec984417c2eec1fcbbb059faba6eb64fcce3abdae00e0f6d36a33"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -39,7 +39,7 @@ "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "reactor": {:hex, :reactor, "0.15.5", "341d9ee664d6141df6639f227692ee6adc8a493d04232dee79e8a4a88e6cef8a", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f9f440ecbdb0c41a832902a692608bd24be621fa7a602819d0dd12971d69f9aa"}, - "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, + "req": {:hex, :req, "0.5.12", "7ce85835867a114c28b6cfc2d8a412f86660290907315ceb173a00e587b853d2", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d65f3d0e7032eb245706554cb5240dbe7a07493154e2dd34e7bb65001aa6ef32"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 85cd60bb..5ff80458 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -791,16 +791,14 @@ defmodule AshPostgres.Test.Post do calculate :relevance_score, :integer, expr( - cond do - fragment( - "ts_rank_cd(to_tsvector(?), ?, 32)::float", - ^ref(:title), - fragment("to_tsquery(?)", ^arg(:query)) - ) > 0.6 -> - 1 - - true -> - 2 + if fragment( + "ts_rank_cd(to_tsvector(?), ?, 32)::float", + ^ref(:title), + fragment("to_tsquery(?)", ^arg(:query)) + ) > 0.6 do + 1 + else + 2 end ) do argument(:query, :string) From 50fc2ba3ed340f072364078f930358a99beca61a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Jun 2025 23:57:05 -0400 Subject: [PATCH 1111/1215] chore: update deps --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 601ad73d..b802109e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.23", "e3c4e508569850fc276780f4ac94bee4c5f2d8e259ec2157c71e8e286db1c3ce", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd491cae52fa73b150e2c3b095ec3ae3aef681666503c300cee60c886d94ebfd"}, - "ash_sql": {:hex, :ash_sql, "0.2.82", "a4fe01ccd2c29ce43af50233e63cd1298735b68ee2c22a1cbb0baa5f31f78ab5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a31f1065b72387b7a19d4e357f06904910b4c4fc8986209975786015f40cf795"}, + "ash": {:hex, :ash, "3.5.24", "47bffb562c39482315d245ce22a381768b1bc16628ba974195630f3ca87d6218", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "881e0decc5e75a0109ca545472b5c6ecb3b28893fec9eaf1866b8c35ddf78c16"}, + "ash_sql": {:hex, :ash_sql, "0.2.83", "de8a9776186d1d1df54e265c1cf0c4e61c1d72be1297c9538f1a32eb9b84de55", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5c99192814177d589d2ba518968b97d1ec7af12446631e3e5538f7a617d7d289"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.8", "f058e7e5e3e69af9c795cc3022a92f802c8e2e1fd366579f6b60af328f69e2bb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b0d9cf65a64ec984417c2eec1fcbbb059faba6eb64fcce3abdae00e0f6d36a33"}, + "igniter": {:hex, :igniter, "0.6.9", "99dd9ea7bcf2fe829617dac660069b3461183e4efbf303dd120fdef96923287d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5fe407e10bc9416f7cd6af90d0409c8226ff2acacb9a7e7b9a097a66c8b5caef"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -44,7 +44,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.66", "b7b47e76961c747f6128ad092c2109dbf742342dec533d3002c35207cb5f6b8e", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "8f0b79839033ab816c2ae37aea29ded83e245a74240e2c931e2396531a9760d6"}, + "spark": {:hex, :spark, "2.2.67", "67626cb9f59ea4b1c5aa85d4afdd025e0740cbd49ed82665d0a40ff007d7fd4b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c8575402e3afc66871362e821bece890536d16319cdb758c5fb2d1250182e46f"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 844f7ba6a40f9c3b52b93c8168b14eca95817559 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Jun 2025 23:58:07 -0400 Subject: [PATCH 1112/1215] chore: release version v2.6.9 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4600e4e0..5cf99e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.9](https://github.com/ash-project/ash_postgres/compare/v2.6.8...v2.6.9) (2025-06-25) + + + + +### Bug Fixes: + +* smallserial not mapping to proper type (#574) by Marc Planelles + +* Fix foreign key constraint on specially named references (#572) by olivermt + ## [v2.6.8](https://github.com/ash-project/ash_postgres/compare/v2.6.7...v2.6.8) (2025-06-18) diff --git a/mix.exs b/mix.exs index af70fd8a..4c59bea1 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.8" + @version "2.6.9" def project do [ From 83abe4fcdabf451a36db09d6ca26820996075536 Mon Sep 17 00:00:00 2001 From: Frank Dugan III Date: Sun, 29 Jun 2025 14:33:06 -0500 Subject: [PATCH 1113/1215] refactor: use multiline string templates to preserve escapes in checks (#578) --- lib/migration_generator/operation.ex | 28 ++++++++++++++++++++---- test/migration_generator_test.exs | 32 ++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 5c8bfbc0..3b266494 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1351,10 +1351,20 @@ defmodule AshPostgres.MigrationGenerator.Operation do }, table: table }) do + prefix = if schema, do: ", " <> option(:prefix, schema), else: "" + if base_filter do - "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"(#{check}) OR NOT (#{base_filter})\")", option(:prefix, schema)])}" + ~s''' + create constraint(:#{as_atom(table)}, :#{as_atom(name)}, check: """ + (#{check}) OR NOT (#{base_filter}) + """#{prefix}) + ''' else - "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"#{check}\")", option(:prefix, schema)])}" + ~s''' + create constraint(:#{as_atom(table)}, :#{as_atom(name)}, check: """ + #{check} + """#{prefix}) + ''' end end @@ -1386,10 +1396,20 @@ defmodule AshPostgres.MigrationGenerator.Operation do schema: schema, table: table }) do + prefix = if schema, do: ", " <> option(:prefix, schema), else: "" + if base_filter do - "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"#{base_filter} AND #{check}\")", option(:prefix, schema)])}" + ~s''' + create constraint(:#{as_atom(table)}, :#{as_atom(name)}, check: """ + #{base_filter} AND #{check} + """#{prefix}) + ''' else - "create constraint(:#{as_atom(table)}, :#{as_atom(name)}, #{join(["check: \"#{check}\")", option(:prefix, schema)])}" + ~s''' + create constraint(:#{as_atom(table)}, :#{as_atom(name)}, check: """ + #{check} + """#{prefix}) + ''' end end end diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index bd8f2c92..e284e847 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -2240,11 +2240,16 @@ defmodule AshPostgres.MigrationGeneratorTest do attributes do uuid_primary_key(:id) attribute(:price, :integer, public?: true) + attribute(:title, :string, public?: true) end postgres do check_constraints do - check_constraint(:price, "price_must_be_positive", check: "price > 0") + check_constraint(:price, "price_must_be_positive", check: ~S["price" > 0]) + + check_constraint(:title, "title_must_conform_to_format", + check: ~S[title ~= '("\"\\"\\\"\\\\"\\\\\")'] + ) end end end @@ -2268,7 +2273,18 @@ defmodule AshPostgres.MigrationGeneratorTest do |> File.read!() assert file =~ - ~S[create constraint(:posts, :price_must_be_positive, check: "price > 0")] + ~S''' + create constraint(:posts, :price_must_be_positive, check: """ + "price" > 0 + """) + ''' + + assert file =~ + ~S''' + create constraint(:posts, :title_must_conform_to_format, check: """ + title ~= '("\"\\"\\\"\\\\"\\\\\")' + """) + ''' defposts do attributes do @@ -2307,7 +2323,11 @@ defmodule AshPostgres.MigrationGeneratorTest do String.split(down, "drop_if_exists constraint(:posts, :price_must_be_positive)") assert remaining =~ - ~S[create constraint(:posts, :price_must_be_positive, check: "price > 0")] + ~S''' + create constraint(:posts, :price_must_be_positive, check: """ + "price" > 0 + """) + ''' end test "base filters are taken into account, negated" do @@ -2349,7 +2369,11 @@ defmodule AshPostgres.MigrationGeneratorTest do |> File.read!() assert file =~ - ~S[create constraint(:posts, :price_must_be_positive, check: "(price > 0) OR NOT (price > -10)")] + ~S''' + create constraint(:posts, :price_must_be_positive, check: """ + (price > 0) OR NOT (price > -10) + """) + ''' end test "when removed, the constraint is dropped before modification" do From 70189697ee962bb1e366a14a4c0bb665e4ae5f6f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 2 Jul 2025 10:12:51 -0400 Subject: [PATCH 1114/1215] test: fix test case to be transactional --- test/references_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/references_test.exs b/test/references_test.exs index 626a2d97..a8be1d07 100644 --- a/test/references_test.exs +++ b/test/references_test.exs @@ -1,5 +1,5 @@ defmodule AshPostgres.ReferencesTest do - use ExUnit.Case + use AshPostgres.RepoCase test "can't use match_type != :full when referencing an non-primary key index" do Code.compiler_options(ignore_module_conflict: true) From b175eb4785ee65978a22afe72cbe10a623cd971b Mon Sep 17 00:00:00 2001 From: kernel-io Date: Thu, 3 Jul 2025 23:42:55 +1200 Subject: [PATCH 1115/1215] test: failing test for parent ref / policy thing (#580) * failing test Signed-off-by: kernel-io * cleaned up and added another failing test Signed-off-by: kernel-io * updated tests Signed-off-by: kernel-io --------- Signed-off-by: kernel-io --- mix.lock | 10 ++++---- test/parent_filter_test.exs | 47 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 15 +++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 test/parent_filter_test.exs diff --git a/mix.lock b/mix.lock index b802109e..7f05569e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.24", "47bffb562c39482315d245ce22a381768b1bc16628ba974195630f3ca87d6218", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "881e0decc5e75a0109ca545472b5c6ecb3b28893fec9eaf1866b8c35ddf78c16"}, - "ash_sql": {:hex, :ash_sql, "0.2.83", "de8a9776186d1d1df54e265c1cf0c4e61c1d72be1297c9538f1a32eb9b84de55", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5c99192814177d589d2ba518968b97d1ec7af12446631e3e5538f7a617d7d289"}, + "ash": {:hex, :ash, "3.5.25", "99f7139e98b745a64312ae80e2420589205b2fec1799f00fc58da771d2c63373", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d45844ea30062b796d4adcad75b8d91e21081ac0f1bb6627d1a2663ca5ecf258"}, + "ash_sql": {:hex, :ash_sql, "0.2.84", "1187555609f4773aacb5cccdca82a78c2b3f7390e78b400a8f03c91b2e7cd82f", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5e6a4d3070e60a0653c572527276a8c034b9458e37b1aca8868b17fcf0a1d1c0"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.9", "99dd9ea7bcf2fe829617dac660069b3461183e4efbf303dd120fdef96923287d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "5fe407e10bc9416f7cd6af90d0409c8226ff2acacb9a7e7b9a097a66c8b5caef"}, + "igniter": {:hex, :igniter, "0.6.10", "896d75fc48ed493ff22accd111fe2e34747163d26c5f374267bad1ef4a6c5076", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9a3abc56e94f362730a3023dfe0ac2ced1186f95fa1ccf4cc30df0c8ce0fc276"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -38,8 +38,8 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, - "reactor": {:hex, :reactor, "0.15.5", "341d9ee664d6141df6639f227692ee6adc8a493d04232dee79e8a4a88e6cef8a", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "f9f440ecbdb0c41a832902a692608bd24be621fa7a602819d0dd12971d69f9aa"}, - "req": {:hex, :req, "0.5.12", "7ce85835867a114c28b6cfc2d8a412f86660290907315ceb173a00e587b853d2", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d65f3d0e7032eb245706554cb5240dbe7a07493154e2dd34e7bb65001aa6ef32"}, + "reactor": {:hex, :reactor, "0.15.6", "d717f9add549b25a089a94c90197718d2d838e35d81dd776b1d81587d4cf2aaa", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "74db98165e3644d86e0f723672d91ceca4339eaa935bcad7e78bf146a46d77b9"}, + "req": {:hex, :req, "0.5.14", "521b449fa0bf275e6d034c05f29bec21789a0d6cd6f7a1c326c7bee642bf6e07", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b7b15692071d556c73432c7797aa7e96b51d1a2db76f746b976edef95c930021"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, diff --git a/test/parent_filter_test.exs b/test/parent_filter_test.exs new file mode 100644 index 00000000..fefbde24 --- /dev/null +++ b/test/parent_filter_test.exs @@ -0,0 +1,47 @@ +defmodule AshPostgres.Test.ParentFilterTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.{Organization, Post, User} + + require Ash.Query + + test "when the first relationship in an `exists` path has parent references in its filter, we don't get error" do + organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "test_org"}) + |> Ash.create!() + + not_my_organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "test_org_2"}) + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{organization_id: not_my_organization.id}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, title: "test_org"}) + |> Ash.create!() + + assert {:ok, [%Post{title: "test_org"}]} = + Post + |> Ash.Query.for_read(:read_with_policy_with_parent) + |> Ash.read( + authorize?: true, + actor: user + ) + + assert {:ok, _} = + Post + |> Ash.Query.filter( + organization.posts.posts_with_my_organization_name_as_a_title.title == "tuna" + ) + |> Ash.read(authorize?: false) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 5ff80458..5da68941 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -92,6 +92,12 @@ defmodule AshPostgres.Test.Post do authorize_if(relates_to_actor_via([:organization, :users])) end + policy action(:read_with_policy_with_parent) do + authorize_if( + relates_to_actor_via([:posts_with_my_organization_name_as_a_title, :organization, :users]) + ) + end + policy action(:allow_any) do authorize_if(always()) end @@ -360,6 +366,9 @@ defmodule AshPostgres.Test.Post do filter(expr(title == "foo")) end + read :read_with_policy_with_parent do + end + read :category_matches do argument(:category, CiCategory) filter(expr(category == ^arg(:category))) @@ -599,6 +608,12 @@ defmodule AshPostgres.Test.Post do filter(expr(^actor(:id) == id)) end + has_many(:posts_with_my_organization_name_as_a_title, __MODULE__) do + public?(true) + no_attributes?(true) + filter(expr(fragment("? = ?", title, parent(organization.name)))) + end + belongs_to :parent_post, __MODULE__ do public?(true) end From 5522c5cae178996285e826eaf3458e45d9d6441a Mon Sep 17 00:00:00 2001 From: Robert Ellen Date: Tue, 8 Jul 2025 11:06:08 +1000 Subject: [PATCH 1116/1215] test: Add tests for combination queries and loading calculations (#586) These tests show that loading calculations in queries that have combinations works. --- test/combination_nullable_calc_test.exs | 197 ++++++++++++++++++++++++ test/support/resources/author.ex | 2 + test/support/resources/post.ex | 2 + 3 files changed, 201 insertions(+) create mode 100644 test/combination_nullable_calc_test.exs diff --git a/test/combination_nullable_calc_test.exs b/test/combination_nullable_calc_test.exs new file mode 100644 index 00000000..1618f1c8 --- /dev/null +++ b/test/combination_nullable_calc_test.exs @@ -0,0 +1,197 @@ +defmodule AshPostgres.CombinationNullableCalcTest do + @moduledoc false + use AshPostgres.RepoCase, async: false + alias AshPostgres.Test.Author + alias AshPostgres.Test.Post + + require Ash.Query + import Ash.Expr + + describe "combination_of with nullable calculations" do + test "combination query with allow_nil? calculation loses ORDER BY" do + Post + |> Ash.Changeset.for_create(:create, %{title: "Zebra", score: 5}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Apple", score: 25}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Dog", score: 10}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Cat", score: 20}) + |> Ash.create!() + + query = + Post + |> Ash.Query.sort([{:title, :asc}]) + |> Ash.Query.load([:latest_comment_title]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(score < 15), + calculations: %{ + sort_order: calc(score * 20, type: :integer) + }, + sort: [{calc(score * 20, type: :integer), :desc}] + ), + Ash.Query.Combination.union( + filter: expr(score >= 15), + calculations: %{ + sort_order: calc(score * 5, type: :integer) + }, + sort: [{calc(score * 5, type: :integer), :desc}] + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + titles = Enum.map(result, & &1.title) + # Expected order: sort_order DESC, then title ASC + # Dog(200), Apple(125), Cat(100), Zebra(100) + expected_title_order = ["Dog", "Apple", "Cat", "Zebra"] + assert titles == expected_title_order + end + + test "combination query without nullable calc works" do + Post + |> Ash.Changeset.for_create(:create, %{title: "Zebra", score: 5}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Apple", score: 25}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Dog", score: 10}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Cat", score: 20}) + |> Ash.create!() + + query = + Post + |> Ash.Query.sort([{:title, :asc}]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(score < 15), + calculations: %{ + sort_order: calc(score * 20, type: :integer) + }, + sort: [{calc(score * 20, type: :integer), :desc}] + ), + Ash.Query.Combination.union( + filter: expr(score >= 15), + calculations: %{ + sort_order: calc(score * 5, type: :integer) + }, + sort: [{calc(score * 5, type: :integer), :desc}] + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + titles = Enum.map(result, & &1.title) + # Expected order: sort_order DESC, then title ASC + # Dog(200), Apple(125), Cat(100), Zebra(100) + expected_title_order = ["Dog", "Apple", "Cat", "Zebra"] + assert titles == expected_title_order + end + end + + describe "Author combination_of with nullable calculations" do + test "Author combination query with allow_nil? calculation loses ORDER BY" do + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Zebra", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Apple", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Dog", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Cat", last_name: "User"}) + |> Ash.create!() + + query = + Author + |> Ash.Query.sort([{:first_name, :asc}]) + |> Ash.Query.load([:profile_description_calc]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(first_name in ["Zebra", "Dog"]), + calculations: %{ + sort_order: calc(1000, type: :integer) + }, + sort: [{calc(1000, type: :integer), :desc}] + ), + Ash.Query.Combination.union( + filter: expr(first_name in ["Apple", "Cat"]), + calculations: %{ + sort_order: calc(500, type: :integer) + }, + sort: [{calc(500, type: :integer), :desc}] + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + first_names = Enum.map(result, & &1.first_name) + # Expected order: sort_order DESC, then first_name ASC + # [Dog, Zebra] (1000), [Apple, Cat] (500) → Dog, Zebra, Apple, Cat + expected_name_order = ["Dog", "Zebra", "Apple", "Cat"] + assert first_names == expected_name_order + end + + test "Author combination query without nullable calc works" do + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Zebra", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Apple", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Dog", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Cat", last_name: "User"}) + |> Ash.create!() + + query = + Author + |> Ash.Query.sort([{:first_name, :asc}]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(first_name in ["Zebra", "Dog"]), + calculations: %{ + sort_order: calc(1000, type: :integer) + } + ), + Ash.Query.Combination.union( + filter: expr(first_name in ["Apple", "Cat"]), + calculations: %{ + sort_order: calc(500, type: :integer) + } + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + first_names = Enum.map(result, & &1.first_name) + # Expected order: sort_order DESC, then first_name ASC + # [Dog, Zebra] (1000), [Apple, Cat] (500) → Dog, Zebra, Apple, Cat + expected_name_order = ["Dog", "Zebra", "Apple", "Cat"] + assert first_names == expected_name_order + end + end +end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index acf9310a..54568518 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -175,6 +175,8 @@ defmodule AshPostgres.Test.Author do calculate(:has_posts, :boolean, expr(exists(posts, true == true))) calculate(:has_no_posts, :boolean, expr(has_posts == false)) + + calculate(:profile_description_calc, :string, expr(profile.description), allow_nil?: true) end aggregates do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 5da68941..94ab170e 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1006,6 +1006,8 @@ defmodule AshPostgres.Test.Post do calculate(:author_first_name_ref_agg_calc, :string, expr(author_first_name)) calculate(:author_profile_description_from_agg, :string, expr(author_profile_description)) + + calculate(:latest_comment_title, :string, expr(latest_comment.title), allow_nil?: true) end aggregates do From 8f5097f779a36dcbde791ff91d9e67d470f2b962 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Jul 2025 21:11:40 -0400 Subject: [PATCH 1117/1215] fix: retain sort when upgrading to a subquery --- lib/data_layer.ex | 1 + test/combination_nullable_calc_test.exs | 197 ------------------------ test/combination_test.exs | 191 +++++++++++++++++++++++ 3 files changed, 192 insertions(+), 197 deletions(-) delete mode 100644 test/combination_nullable_calc_test.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 22efd25e..0a8b4644 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3445,6 +3445,7 @@ defmodule AshPostgres.DataLayer do &Map.merge(&1, %{ already_selected: fieldset, subquery_upgrade?: true, + sort: query.__ash_bindings__[:sort], context: query.__ash_bindings__.context }) ) diff --git a/test/combination_nullable_calc_test.exs b/test/combination_nullable_calc_test.exs deleted file mode 100644 index 1618f1c8..00000000 --- a/test/combination_nullable_calc_test.exs +++ /dev/null @@ -1,197 +0,0 @@ -defmodule AshPostgres.CombinationNullableCalcTest do - @moduledoc false - use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.Author - alias AshPostgres.Test.Post - - require Ash.Query - import Ash.Expr - - describe "combination_of with nullable calculations" do - test "combination query with allow_nil? calculation loses ORDER BY" do - Post - |> Ash.Changeset.for_create(:create, %{title: "Zebra", score: 5}) - |> Ash.create!() - - Post - |> Ash.Changeset.for_create(:create, %{title: "Apple", score: 25}) - |> Ash.create!() - - Post - |> Ash.Changeset.for_create(:create, %{title: "Dog", score: 10}) - |> Ash.create!() - - Post - |> Ash.Changeset.for_create(:create, %{title: "Cat", score: 20}) - |> Ash.create!() - - query = - Post - |> Ash.Query.sort([{:title, :asc}]) - |> Ash.Query.load([:latest_comment_title]) - |> Ash.Query.combination_of([ - Ash.Query.Combination.base( - filter: expr(score < 15), - calculations: %{ - sort_order: calc(score * 20, type: :integer) - }, - sort: [{calc(score * 20, type: :integer), :desc}] - ), - Ash.Query.Combination.union( - filter: expr(score >= 15), - calculations: %{ - sort_order: calc(score * 5, type: :integer) - }, - sort: [{calc(score * 5, type: :integer), :desc}] - ) - ]) - |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) - - result = Ash.read!(query) - titles = Enum.map(result, & &1.title) - # Expected order: sort_order DESC, then title ASC - # Dog(200), Apple(125), Cat(100), Zebra(100) - expected_title_order = ["Dog", "Apple", "Cat", "Zebra"] - assert titles == expected_title_order - end - - test "combination query without nullable calc works" do - Post - |> Ash.Changeset.for_create(:create, %{title: "Zebra", score: 5}) - |> Ash.create!() - - Post - |> Ash.Changeset.for_create(:create, %{title: "Apple", score: 25}) - |> Ash.create!() - - Post - |> Ash.Changeset.for_create(:create, %{title: "Dog", score: 10}) - |> Ash.create!() - - Post - |> Ash.Changeset.for_create(:create, %{title: "Cat", score: 20}) - |> Ash.create!() - - query = - Post - |> Ash.Query.sort([{:title, :asc}]) - |> Ash.Query.combination_of([ - Ash.Query.Combination.base( - filter: expr(score < 15), - calculations: %{ - sort_order: calc(score * 20, type: :integer) - }, - sort: [{calc(score * 20, type: :integer), :desc}] - ), - Ash.Query.Combination.union( - filter: expr(score >= 15), - calculations: %{ - sort_order: calc(score * 5, type: :integer) - }, - sort: [{calc(score * 5, type: :integer), :desc}] - ) - ]) - |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) - - result = Ash.read!(query) - titles = Enum.map(result, & &1.title) - # Expected order: sort_order DESC, then title ASC - # Dog(200), Apple(125), Cat(100), Zebra(100) - expected_title_order = ["Dog", "Apple", "Cat", "Zebra"] - assert titles == expected_title_order - end - end - - describe "Author combination_of with nullable calculations" do - test "Author combination query with allow_nil? calculation loses ORDER BY" do - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Zebra", last_name: "User"}) - |> Ash.create!() - - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Apple", last_name: "User"}) - |> Ash.create!() - - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Dog", last_name: "User"}) - |> Ash.create!() - - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Cat", last_name: "User"}) - |> Ash.create!() - - query = - Author - |> Ash.Query.sort([{:first_name, :asc}]) - |> Ash.Query.load([:profile_description_calc]) - |> Ash.Query.combination_of([ - Ash.Query.Combination.base( - filter: expr(first_name in ["Zebra", "Dog"]), - calculations: %{ - sort_order: calc(1000, type: :integer) - }, - sort: [{calc(1000, type: :integer), :desc}] - ), - Ash.Query.Combination.union( - filter: expr(first_name in ["Apple", "Cat"]), - calculations: %{ - sort_order: calc(500, type: :integer) - }, - sort: [{calc(500, type: :integer), :desc}] - ) - ]) - |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) - - result = Ash.read!(query) - first_names = Enum.map(result, & &1.first_name) - # Expected order: sort_order DESC, then first_name ASC - # [Dog, Zebra] (1000), [Apple, Cat] (500) → Dog, Zebra, Apple, Cat - expected_name_order = ["Dog", "Zebra", "Apple", "Cat"] - assert first_names == expected_name_order - end - - test "Author combination query without nullable calc works" do - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Zebra", last_name: "User"}) - |> Ash.create!() - - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Apple", last_name: "User"}) - |> Ash.create!() - - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Dog", last_name: "User"}) - |> Ash.create!() - - Author - |> Ash.Changeset.for_create(:create, %{first_name: "Cat", last_name: "User"}) - |> Ash.create!() - - query = - Author - |> Ash.Query.sort([{:first_name, :asc}]) - |> Ash.Query.combination_of([ - Ash.Query.Combination.base( - filter: expr(first_name in ["Zebra", "Dog"]), - calculations: %{ - sort_order: calc(1000, type: :integer) - } - ), - Ash.Query.Combination.union( - filter: expr(first_name in ["Apple", "Cat"]), - calculations: %{ - sort_order: calc(500, type: :integer) - } - ) - ]) - |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) - - result = Ash.read!(query) - first_names = Enum.map(result, & &1.first_name) - # Expected order: sort_order DESC, then first_name ASC - # [Dog, Zebra] (1000), [Apple, Cat] (500) → Dog, Zebra, Apple, Cat - expected_name_order = ["Dog", "Zebra", "Apple", "Cat"] - assert first_names == expected_name_order - end - end -end diff --git a/test/combination_test.exs b/test/combination_test.exs index 086ae91d..5faaa020 100644 --- a/test/combination_test.exs +++ b/test/combination_test.exs @@ -5,6 +5,9 @@ defmodule AshPostgres.CombinationTest do require Ash.Query import Ash.Expr + alias AshPostgres.Test.Author + alias AshPostgres.Test.Post + describe "combinations in actions" do test "with no data" do Post @@ -427,4 +430,192 @@ defmodule AshPostgres.CombinationTest do |> Enum.map(&to_string(&1.category)) end end + + describe "combination_of with nullable calculations" do + test "combination query with allow_nil? calculation loses ORDER BY" do + Post + |> Ash.Changeset.for_create(:create, %{title: "Zebra", score: 5}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Apple", score: 25}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Dog", score: 10}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Cat", score: 20}) + |> Ash.create!() + + query = + Post + |> Ash.Query.sort([{:title, :asc}]) + |> Ash.Query.load([:latest_comment_title]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(score < 15), + calculations: %{ + sort_order: calc(score * 20, type: :integer) + }, + sort: [{calc(score * 20, type: :integer), :desc}] + ), + Ash.Query.Combination.union( + filter: expr(score >= 15), + calculations: %{ + sort_order: calc(score * 5, type: :integer) + }, + sort: [{calc(score * 5, type: :integer), :desc}] + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + titles = Enum.map(result, & &1.title) + # Expected order: sort_order DESC, then title ASC + # Dog(200), Apple(125), Cat(100), Zebra(100) + expected_title_order = ["Dog", "Apple", "Cat", "Zebra"] + assert titles == expected_title_order + end + + test "combination query without nullable calc works" do + Post + |> Ash.Changeset.for_create(:create, %{title: "Zebra", score: 5}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Apple", score: 25}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Dog", score: 10}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{title: "Cat", score: 20}) + |> Ash.create!() + + query = + Post + |> Ash.Query.sort([{:title, :asc}]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(score < 15), + calculations: %{ + sort_order: calc(score * 20, type: :integer) + }, + sort: [{calc(score * 20, type: :integer), :desc}] + ), + Ash.Query.Combination.union( + filter: expr(score >= 15), + calculations: %{ + sort_order: calc(score * 5, type: :integer) + }, + sort: [{calc(score * 5, type: :integer), :desc}] + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + titles = Enum.map(result, & &1.title) + # Expected order: sort_order DESC, then title ASC + # Dog(200), Apple(125), Cat(100), Zebra(100) + expected_title_order = ["Dog", "Apple", "Cat", "Zebra"] + assert titles == expected_title_order + end + end + + describe "Author combination_of with nullable calculations" do + test "Author combination query with allow_nil? calculation loses ORDER BY" do + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Zebra", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Apple", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Dog", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Cat", last_name: "User"}) + |> Ash.create!() + + query = + Author + |> Ash.Query.sort([{:first_name, :asc}]) + |> Ash.Query.load([:profile_description_calc]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(first_name in ["Zebra", "Dog"]), + calculations: %{ + sort_order: calc(1000, type: :integer) + }, + sort: [{calc(1000, type: :integer), :desc}] + ), + Ash.Query.Combination.union( + filter: expr(first_name in ["Apple", "Cat"]), + calculations: %{ + sort_order: calc(500, type: :integer) + }, + sort: [{calc(500, type: :integer), :desc}] + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + first_names = Enum.map(result, & &1.first_name) + # Expected order: sort_order DESC, then first_name ASC + # [Dog, Zebra] (1000), [Apple, Cat] (500) → Dog, Zebra, Apple, Cat + expected_name_order = ["Dog", "Zebra", "Apple", "Cat"] + assert first_names == expected_name_order + end + + test "Author combination query without nullable calc works" do + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Zebra", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Apple", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Dog", last_name: "User"}) + |> Ash.create!() + + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Cat", last_name: "User"}) + |> Ash.create!() + + query = + Author + |> Ash.Query.sort([{:first_name, :asc}]) + |> Ash.Query.combination_of([ + Ash.Query.Combination.base( + filter: expr(first_name in ["Zebra", "Dog"]), + calculations: %{ + sort_order: calc(1000, type: :integer) + } + ), + Ash.Query.Combination.union( + filter: expr(first_name in ["Apple", "Cat"]), + calculations: %{ + sort_order: calc(500, type: :integer) + } + ) + ]) + |> Ash.Query.sort([{calc(^combinations(:sort_order)), :desc}], prepend?: true) + + result = Ash.read!(query) + first_names = Enum.map(result, & &1.first_name) + # Expected order: sort_order DESC, then first_name ASC + # [Dog, Zebra] (1000), [Apple, Cat] (500) → Dog, Zebra, Apple, Cat + expected_name_order = ["Dog", "Zebra", "Apple", "Cat"] + assert first_names == expected_name_order + end + end end From 986c4c724ea098c4679be271869f1d598ad287e6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Jul 2025 21:25:02 -0400 Subject: [PATCH 1118/1215] fix: properly return the type when configured closes #588 --- lib/resource_generator/spec.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index ff942f16..8b6d4ae6 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -908,7 +908,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do - MyApp.Types.CustomType - {:array, :string} - Use `skip` to skip ignore this attribute. + Use `skip` to ignore this attribute. """) end @@ -921,13 +921,12 @@ defmodule AshPostgres.ResourceGenerator.Spec do ":" <> type -> new_type = String.to_atom(type) Process.put({:type_cache, attribute.type}, new_type) - {:ok, %{attribute | attr_type: new_type}} + {:ok, new_type} type -> try do - Code.eval_string(type) Process.put({:type_cache, attribute.type}, new_type) - {:ok, %{attribute | attr_type: new_type}} + {:ok, type} rescue _e -> get_type(attribute, opts) From 50d9d0f02c0f5747b30d74012f40a649c265ca76 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 8 Jul 2025 23:10:22 -0400 Subject: [PATCH 1119/1215] chore: update deps --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 7f05569e..297b4075 100644 --- a/mix.lock +++ b/mix.lock @@ -18,12 +18,12 @@ "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, - "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.10", "896d75fc48ed493ff22accd111fe2e34747163d26c5f374267bad1ef4a6c5076", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9a3abc56e94f362730a3023dfe0ac2ced1186f95fa1ccf4cc30df0c8ce0fc276"}, + "igniter": {:hex, :igniter, "0.6.12", "a8e20a67c2729dd0e03ccb06799efb23017fbdbed939ac6a88d8ddcc1a005bf7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4ba183c8fa299ac1207c078c3d055c635b418d6573006a2e92c131dfd1879a92"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -47,7 +47,7 @@ "spark": {:hex, :spark, "2.2.67", "67626cb9f59ea4b1c5aa85d4afdd025e0740cbd49ed82665d0a40ff007d7fd4b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c8575402e3afc66871362e821bece890536d16319cdb758c5fb2d1250182e46f"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, - "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, + "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, From a0314239db57fff2046b80cc70e482fced54d99a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 8 Jul 2025 23:11:22 -0400 Subject: [PATCH 1120/1215] chore: release version v2.6.10 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf99e8d..b5d718f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.10](https://github.com/ash-project/ash_postgres/compare/v2.6.9...v2.6.10) (2025-07-09) + + + + +### Bug Fixes: + +* properly return the type when configured by Zach Daniel + +* retain sort when upgrading to a subquery by Zach Daniel + ## [v2.6.9](https://github.com/ash-project/ash_postgres/compare/v2.6.8...v2.6.9) (2025-06-25) diff --git a/mix.exs b/mix.exs index 4c59bea1..c954d13a 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.9" + @version "2.6.10" def project do [ From 106a532e95ffe393f387ca518e84b5b38a6b5303 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 8 Jul 2025 23:11:46 -0400 Subject: [PATCH 1121/1215] chore: update more deps --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 297b4075..a09948cd 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.25", "99f7139e98b745a64312ae80e2420589205b2fec1799f00fc58da771d2c63373", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d45844ea30062b796d4adcad75b8d91e21081ac0f1bb6627d1a2663ca5ecf258"}, - "ash_sql": {:hex, :ash_sql, "0.2.84", "1187555609f4773aacb5cccdca82a78c2b3f7390e78b400a8f03c91b2e7cd82f", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "5e6a4d3070e60a0653c572527276a8c034b9458e37b1aca8868b17fcf0a1d1c0"}, + "ash": {:hex, :ash, "3.5.26", "f2e884623bfc39e0228a4fee0c41fbdb90195c6b1e1618a0a97f03f2dfbb1c4f", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9f5edbb7d838e2053e84f62c428650dadbff3ea4ec40ef68f167483eb7e9012"}, + "ash_sql": {:hex, :ash_sql, "0.2.85", "96a35d197f5ff846c17aca9225d4baafa0fda6c804f1e46a79345d237b5e5c5f", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "62e7f8b79bcb04d82654ba519008b80bd21bd177ec646e9fffb87ec34285722b"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From fdb077e7b52b1ecf50352ff18446a960480d14e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Wed, 9 Jul 2025 22:02:33 +0200 Subject: [PATCH 1122/1215] fix: Reverse migrations order when reverting dev migrations (#590) --- lib/migration_generator/migration_generator.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index a5984a67..84630f7d 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -556,6 +556,7 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) |> Enum.sort() + |> Enum.reverse() |> Enum.filter(fn {version, _} -> version in versions end) @@ -589,6 +590,7 @@ defmodule AshPostgres.MigrationGenerator do |> Enum.filter(& &1) |> Enum.map(&load_migration!/1) |> Enum.sort() + |> Enum.reverse() |> Enum.filter(fn {version, _} -> version in versions end) From d174139867ace7027b6a97fcd557ff1d0a31592d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 9 Jul 2025 17:34:56 -0400 Subject: [PATCH 1123/1215] chore: update installer for latest igniter changes --- lib/mix/tasks/ash_postgres.install.ex | 11 +++++++++-- mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 73f50db0..a984f2d8 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -200,12 +200,12 @@ if Code.ensure_loaded?(Igniter) do ) do {:ok, _zipper} -> zipper - |> Igniter.Project.Config.modify_configuration_code( + |> modify_configuration_code( [repo, :url], otp_app, {:database_url, [], nil} ) - |> Igniter.Project.Config.modify_configuration_code( + |> modify_configuration_code( [repo, :pool_size], otp_app, Sourceror.parse_string!(""" @@ -252,6 +252,13 @@ if Code.ensure_loaded?(Igniter) do end end + defp modify_configuration_code(zipper, path, otp_app, code) do + case Igniter.Project.Config.modify_config_code(zipper, path, otp_app, code) do + {:ok, zipper} -> zipper + _ -> zipper + end + end + defp configure_dev(igniter, otp_app, repo) do if Igniter.Project.Config.configures_key?(igniter, "dev.exs", otp_app, [repo]) do igniter diff --git a/mix.exs b/mix.exs index c954d13a..4aa37c8e 100644 --- a/mix.exs +++ b/mix.exs @@ -168,7 +168,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.5 and >= 3.5.13")}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, - {:igniter, "~> 0.6", optional: true}, + {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, {:jason, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index a09948cd..e27a5efc 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.12", "a8e20a67c2729dd0e03ccb06799efb23017fbdbed939ac6a88d8ddcc1a005bf7", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4ba183c8fa299ac1207c078c3d055c635b418d6573006a2e92c131dfd1879a92"}, + "igniter": {:hex, :igniter, "0.6.14", "c7d1aa437bd87389a724ab8971ed4f4b645d844f55cb9dfa2d3933a8350f5d68", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "2d566654e3ee33351307053869235c51bed25fb2c0af491896fd91c3315ecb8c"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From de90abdd71c10d26bdb7907856511d414b68e7ca Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 13 Jul 2025 22:01:25 -0400 Subject: [PATCH 1124/1215] improvement: make rollbacks safer by using `--to` instead of `-n` --- lib/data_layer.ex | 60 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 0a8b4644..b0000ee2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -459,10 +459,14 @@ defmodule AshPostgres.DataLayer do end) |> Enum.take(20) |> Enum.map(&String.trim_leading(&1, migrations_path)) + |> Enum.map(&String.trim_leading(&1, "/")) + + indexed = + files |> Enum.with_index() |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - n = + to = Mix.shell().prompt( """ How many migrations should be rolled back#{for_repo}? (default: 0) @@ -470,7 +474,7 @@ defmodule AshPostgres.DataLayer do Last 20 migration names, with the input you must provide to rollback up to *and including* that migration: - #{Enum.join(files, "\n")} + #{Enum.join(indexed, "\n")} Rollback to: """ |> String.trim_trailing() @@ -478,19 +482,32 @@ defmodule AshPostgres.DataLayer do |> String.trim() |> case do "" -> - 0 + nil + + "0" -> + nil n -> try do - String.to_integer(n) + files + |> Enum.at(String.to_integer(n) - 1) rescue _ -> reraise "Required an integer value, got: #{n}", __STACKTRACE__ end + |> String.split("_", parts: 2) + |> Enum.at(0) + |> String.to_integer() end - Mix.Task.run("ash_postgres.rollback", args ++ ["-r", inspect(repo), "-n", to_string(n)]) - Mix.Task.reenable("ash_postgres.rollback") + if to do + Mix.Task.run( + "ash_postgres.rollback", + args ++ ["-r", inspect(repo), "--to", to_string(to)] + ) + + Mix.Task.reenable("ash_postgres.rollback") + end tenant_files = tenant_migrations_path @@ -520,10 +537,14 @@ defmodule AshPostgres.DataLayer do end) |> Enum.take(20) |> Enum.map(&String.trim_leading(&1, tenant_migrations_path)) + |> Enum.map(&String.trim_leading(&1, "/")) + + indexed = + tenant_files |> Enum.with_index() |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - n = + to = Mix.shell().prompt( """ @@ -536,7 +557,7 @@ defmodule AshPostgres.DataLayer do Last 20 migration names, with the input you must provide to rollback up to *and including* that migration: - #{Enum.join(tenant_files, "\n")} + #{Enum.join(indexed, "\n")} Rollback to: """ @@ -545,23 +566,32 @@ defmodule AshPostgres.DataLayer do |> String.trim() |> case do "" -> - 0 + nil + + "0" -> + nil n -> try do - String.to_integer(n) + tenant_files + |> Enum.at(String.to_integer(n) - 1) rescue _ -> reraise "Required an integer value, got: #{n}", __STACKTRACE__ end + |> String.split("_", parts: 2) + |> Enum.at(0) + |> String.to_integer() end - Mix.Task.run( - "ash_postgres.rollback", - args ++ ["--tenants", "-r", inspect(repo), "-n", to_string(n)] - ) + if to do + Mix.Task.run( + "ash_postgres.rollback", + args ++ ["--tenants", "-r", inspect(repo), "--to", to] + ) - Mix.Task.reenable("ash_postgres.rollback") + Mix.Task.reenable("ash_postgres.rollback") + end end end end) From 1f064f52b51e7661aee0fde95a9c719a0f1ad70e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 14 Jul 2025 13:48:00 -0400 Subject: [PATCH 1125/1215] fix: clean args and properly scope rollback task closes #592 --- lib/data_layer.ex | 10 +++++++++- lib/mix/helpers.ex | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b0000ee2..d527bce6 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -424,7 +424,15 @@ defmodule AshPostgres.DataLayer do end def rollback(args) do - repos = AshPostgres.Mix.Helpers.repos!([], args) + {opts, _, _} = + OptionParser.parse(args, + switches: [ + repo: :string + ], + aliases: [r: :repo] + ) + + repos = AshPostgres.Mix.Helpers.repos!(opts, args) show_for_repo? = Enum.count_until(repos, 2) == 2 diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 2eb2d40b..316778fd 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -119,7 +119,7 @@ defmodule AshPostgres.Mix.Helpers do def delete_flag(args, arg) do case Enum.split_while(args, &(&1 != arg)) do {left, [_ | rest]} -> - left ++ rest + delete_flag(left ++ rest, arg) _ -> args @@ -129,7 +129,7 @@ defmodule AshPostgres.Mix.Helpers do def delete_arg(args, arg) do case Enum.split_while(args, &(&1 != arg)) do {left, [_, _ | rest]} -> - left ++ rest + delete_arg(left ++ rest, arg) _ -> args From 8787615f38e276c8b37d405f010b1c044d0ed834 Mon Sep 17 00:00:00 2001 From: Jesse Williams Date: Tue, 15 Jul 2025 16:26:43 -0700 Subject: [PATCH 1126/1215] test: reproduce a bug with a complex calculation (#593) * reproduce a bug with a complex calculation * no unused vars --- .../20250714225304.json | 86 +++++++++++++++++++ .../20250714225304.json | 55 ++++++++++++ ..._complex_calculations_folder_and_items.exs | 61 +++++++++++++ test/complex_calculations_test.exs | 30 +++++++ test/support/complex_calculations/domain.ex | 2 + .../complex_calculations/resources/folder.ex | 75 ++++++++++++++++ .../resources/folder_item.ex | 38 ++++++++ 7 files changed, 347 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json create mode 100644 priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs create mode 100644 test/support/complex_calculations/resources/folder.ex create mode 100644 test/support/complex_calculations/resources/folder_item.ex diff --git a/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json b/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json new file mode 100644 index 00000000..f217c546 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json @@ -0,0 +1,86 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "level", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "complex_calculations_folder_items_folder_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "complex_calculations_folders" + }, + "scale": null, + "size": null, + "source": "folder_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "C7BC97676F202A744482B4095560986B265E6BCB74F6E69C1330034FBC4981E5", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "complex_calculations_folder_items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json b/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json new file mode 100644 index 00000000..8d499706 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json @@ -0,0 +1,55 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "some_integer_setting", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "level", + "type": "ltree" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": false, + "hash": "1982E0DAF0B5B22502A5747ED8533C6F1D58E882E1132FAB0939EC13F2CFC5AF", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "complex_calculations_folders" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs b/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs new file mode 100644 index 00000000..88d73d15 --- /dev/null +++ b/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs @@ -0,0 +1,61 @@ +defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationsFolderAndItems 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 + create table(:complex_calculations_folder_items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text) + add(:level, :bigint) + add(:folder_id, :uuid) + end + + create table(:complex_calculations_folders, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:complex_calculations_folder_items) do + modify( + :folder_id, + references(:complex_calculations_folders, + column: :id, + name: "complex_calculations_folder_items_folder_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:complex_calculations_folders) do + add(:some_integer_setting, :bigint) + add(:level, :ltree) + end + end + + def down do + alter table(:complex_calculations_folders) do + remove(:level) + remove(:some_integer_setting) + end + + drop( + constraint( + :complex_calculations_folder_items, + "complex_calculations_folder_items_folder_id_fkey" + ) + ) + + alter table(:complex_calculations_folder_items) do + modify(:folder_id, :uuid) + end + + drop(table(:complex_calculations_folders)) + + drop(table(:complex_calculations_folder_items)) + end +end diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 83cb39cf..5617a6d1 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -373,4 +373,34 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do assert doc_before.is_active_with_timezone == false assert doc_after.is_active_with_timezone == true end + + test "gnarly parent bug with some weird ltree setup" do + _folder_a = + Ash.Seed.seed!( + AshPostgres.Test.Support.ComplexCalculations.Folder, + %{some_integer_setting: 1, level: "a"} + ) + + _folder_b = + Ash.Seed.seed!( + AshPostgres.Test.Support.ComplexCalculations.Folder, + %{some_integer_setting: nil, level: "a.b"} + ) + + folder_c = + Ash.Seed.seed!( + AshPostgres.Test.Support.ComplexCalculations.Folder, + %{some_integer_setting: nil, level: "a.b.c"} + ) + + folder_c_item = + Ash.Seed.seed!( + AshPostgres.Test.Support.ComplexCalculations.FolderItem, + %{name: "Item in C", level: 1, folder_id: folder_c.id} + ) + + # reproduction: this raises Unknown Error + # * ** (Postgrex.Error) ERROR 42846 (cannot_coerce) cannot cast type bigint to ltree + assert Ash.calculate!(folder_c_item, :folder_setting) == 1 + end end diff --git a/test/support/complex_calculations/domain.ex b/test/support/complex_calculations/domain.ex index 76d066c2..15a306ef 100644 --- a/test/support/complex_calculations/domain.ex +++ b/test/support/complex_calculations/domain.ex @@ -9,6 +9,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Domain do resource(AshPostgres.Test.ComplexCalculations.Channel) resource(AshPostgres.Test.ComplexCalculations.DMChannel) resource(AshPostgres.Test.ComplexCalculations.ChannelMember) + resource(AshPostgres.Test.Support.ComplexCalculations.Folder) + resource(AshPostgres.Test.Support.ComplexCalculations.FolderItem) end authorization do diff --git a/test/support/complex_calculations/resources/folder.ex b/test/support/complex_calculations/resources/folder.ex new file mode 100644 index 00000000..d4b31e38 --- /dev/null +++ b/test/support/complex_calculations/resources/folder.ex @@ -0,0 +1,75 @@ +defmodule AshPostgres.Test.Support.ComplexCalculations.Folder do + @moduledoc """ + A tree structure using the ltree type. + """ + + alias AshPostgres.Test.Support.ComplexCalculations.Folder + + use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, + data_layer: AshPostgres.DataLayer + + @default_integer_setting 5 + + postgres do + table "complex_calculations_folders" + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + + attribute(:some_integer_setting, :integer, + public?: true, + description: "Some setting that can be inherited. No real semantic meaning, just for demo" + ) + + attribute(:level, AshPostgres.Ltree, public?: true) + end + + actions do + defaults([:read]) + end + + relationships do + has_many :ancestors, Folder do + public?(true) + no_attributes?(true) + + # use ltree @> operator to get all ancestors + filter(expr(fragment("? @> ? AND ? < ?", level, parent(level), nlevel, parent(nlevel)))) + end + + has_many :items, AshPostgres.Test.Support.ComplexCalculations.FolderItem do + public?(true) + end + end + + calculations do + calculate(:nlevel, :integer, expr(fragment("nlevel(?)", level))) + + calculate( + :effective_integer_setting, + :integer, + expr( + if is_nil(some_integer_setting) do + # closest ancestor with a non-nil setting, or the default + first( + ancestors, + query: [ + sort: [nlevel: :desc], + filter: expr(not is_nil(some_integer_setting)) + ], + field: :some_integer_setting + ) || @default_integer_setting + else + some_integer_setting + end + ), + description: """ + The effective integer setting, inheriting from ancestors if not explicitly set, + or defaulting to #{@default_integer_setting} if none are set. No real semantic meaning, just for demo + """ + ) + end +end diff --git a/test/support/complex_calculations/resources/folder_item.ex b/test/support/complex_calculations/resources/folder_item.ex new file mode 100644 index 00000000..ac115aef --- /dev/null +++ b/test/support/complex_calculations/resources/folder_item.ex @@ -0,0 +1,38 @@ +defmodule AshPostgres.Test.Support.ComplexCalculations.FolderItem do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.ComplexCalculations.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "complex_calculations_folder_items" + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + + attribute(:level, :integer, + public?: true, + description: """ + No real semantic meaning here, just for demo, this *deliberately* is named the same as the ltree level column in Folder, + but is NOT related to it in any way + """ + ) + end + + actions do + defaults([:read]) + end + + calculations do + calculate(:folder_setting, :integer, expr(folder.effective_integer_setting)) + end + + relationships do + belongs_to(:folder, AshPostgres.Test.Support.ComplexCalculations.Folder) do + public?(true) + end + end +end From ce21b9239aeb85240082fe5f2657894e2a078031 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 17 Jul 2025 10:48:53 -0400 Subject: [PATCH 1127/1215] chore: release version v2.6.11 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d718f2..996183c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.11](https://github.com/ash-project/ash_postgres/compare/v2.6.10...v2.6.11) (2025-07-17) + + + + +### Bug Fixes: + +* clean args and properly scope rollback task by Zach Daniel + +* Reverse migrations order when reverting dev migrations (#590) by Kenneth Kostrešević + +### Improvements: + +* make rollbacks safer by using `--to` instead of `-n` by Zach Daniel + ## [v2.6.10](https://github.com/ash-project/ash_postgres/compare/v2.6.9...v2.6.10) (2025-07-09) diff --git a/mix.exs b/mix.exs index 4aa37c8e..68afd199 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.10" + @version "2.6.11" def project do [ From db24c4e4a55355c410173456b7be7eebc3187004 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 17 Jul 2025 10:49:25 -0400 Subject: [PATCH 1128/1215] chore: update deps --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index e27a5efc..b0c5a864 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.26", "f2e884623bfc39e0228a4fee0c41fbdb90195c6b1e1618a0a97f03f2dfbb1c4f", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9f5edbb7d838e2053e84f62c428650dadbff3ea4ec40ef68f167483eb7e9012"}, - "ash_sql": {:hex, :ash_sql, "0.2.85", "96a35d197f5ff846c17aca9225d4baafa0fda6c804f1e46a79345d237b5e5c5f", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "62e7f8b79bcb04d82654ba519008b80bd21bd177ec646e9fffb87ec34285722b"}, + "ash": {:hex, :ash, "3.5.27", "bfa227b75da2b447d1b98e16a19b2fa957fe32bef33dfe33aa93b861a532c641", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f75694b0012e4e56293e1beef1d6c20a52f2f2c9baebfa5c8f2426d25d43608e"}, + "ash_sql": {:hex, :ash_sql, "0.2.86", "9b4013010981352e295eed22dc6b09998e23a724baca07f1ff617b6bec4ba8d4", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "97fa13e87d55194f7746c5a9fa97a9693bc26cdf7825d1c6d7cdfe5f166285a4"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.14", "c7d1aa437bd87389a724ab8971ed4f4b645d844f55cb9dfa2d3933a8350f5d68", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "2d566654e3ee33351307053869235c51bed25fb2c0af491896fd91c3315ecb8c"}, + "igniter": {:hex, :igniter, "0.6.19", "d87703b36890bc4278341d966a7ed8e10604a18610a4331ac10c75d1af48fff4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c2070b3fdbd238fc0a0bfbc1f125b5c0f79a1fe2f5b3c7b43cd33de696783663"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -39,7 +39,7 @@ "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "reactor": {:hex, :reactor, "0.15.6", "d717f9add549b25a089a94c90197718d2d838e35d81dd776b1d81587d4cf2aaa", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "74db98165e3644d86e0f723672d91ceca4339eaa935bcad7e78bf146a46d77b9"}, - "req": {:hex, :req, "0.5.14", "521b449fa0bf275e6d034c05f29bec21789a0d6cd6f7a1c326c7bee642bf6e07", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b7b15692071d556c73432c7797aa7e96b51d1a2db76f746b976edef95c930021"}, + "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, @@ -54,5 +54,5 @@ "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, - "ymlr": {:hex, :ymlr, "5.1.3", "a8061add5a378e20272a31905be70209a5680fdbe0ad51f40cb1af4bdd0a010b", [:mix], [], "hexpm", "8663444fa85101a117887c170204d4c5a2182567e5f84767f0071cf15f2efb1e"}, + "ymlr": {:hex, :ymlr, "5.1.4", "b924d61e1fc1ec371cde6ab3ccd9311110b1e052fc5c2460fb322e8380e7712a", [:mix], [], "hexpm", "75f16cf0709fbd911b30311a0359a7aa4b5476346c01882addefd5f2b1cfaa51"}, } From 5428e89671872cf585e07d71ae6090bc9c2e111e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 20 Jul 2025 23:14:40 -0400 Subject: [PATCH 1129/1215] chore: tests for aggregate error w/ modify_query --- test/aggregate_test.exs | 11 +++++++++++ test/support/resources/comment.ex | 15 +++++++++++++++ test/support/resources/post.ex | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 167a00c9..d17b753b 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1667,4 +1667,15 @@ defmodule AshSql.AggregateTest do Ash.Query.filter_input(AshPostgres.Test.StandupClub, filter) |> Ash.read!(load: [:punchline_count]) end + + @tag :regression + test "aggregates with modify_query raise an appropriate error" do + assert_raise Ash.Error.Unknown, ~r/does not currently support aggregates/, fn -> + Post + |> Ash.Query.load([ + :count_comments_with_modify_query + ]) + |> Ash.read_one!() + end + end end diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index fe4a818e..517c42af 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -33,6 +33,10 @@ defmodule AshPostgres.Test.Comment do change(manage_relationship(:rating, :ratings, on_missing: :ignore, on_match: :create)) end + + read :with_modify_query do + modify_query({AshPostgres.Test.Comment.ModifyQuery, :modify, []}) + end end attributes do @@ -104,3 +108,14 @@ defmodule AshPostgres.Test.Comment do ) end end + +defmodule AshPostgres.Test.Comment.ModifyQuery do + @moduledoc """ + Raises when modifying query so we can assert + this code path is called. + """ + + def modify(_ash_query, _ecto_query) do + raise "modifying query!" + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 94ab170e..79eddd34 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1164,6 +1164,10 @@ defmodule AshPostgres.Test.Post do end first(:author_profile_description, :author, :description) + + count :count_comments_with_modify_query, :comments do + read_action(:with_modify_query) + end end end From b24b845ae35b7654fc613d60d02c3a3aaf86ceef Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 22 Jul 2025 15:09:00 -0400 Subject: [PATCH 1130/1215] Update dependabot schedule to monthly --- .github/dependabot.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a3168e45..597c8ec7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,14 @@ -version: 2 +--- updates: - - package-ecosystem: mix - directory: "/" - versioning-strategy: lockfile-only - schedule: - interval: weekly - day: thursday + - directory: / groups: - production-dependencies: - dependency-type: production dev-dependencies: dependency-type: development + production-dependencies: + dependency-type: production + package-ecosystem: mix + schedule: + day: thursday + interval: monthly + versioning-strategy: lockfile-only +version: 2 From 5bbc43fa64b69b4dfd18957f0be151fb6e37cceb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:20:32 -0400 Subject: [PATCH 1131/1215] chore(deps): bump the production-dependencies group with 3 updates (#598) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [igniter](https://github.com/ash-project/igniter). Updates `ash` from 3.5.27 to 3.5.31 - [Release notes](https://github.com/ash-project/ash/releases) - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.27...v3.5.31) Updates `ash_sql` from 0.2.86 to 0.2.87 - [Release notes](https://github.com/ash-project/ash_sql/releases) - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.86...v0.2.87) Updates `igniter` from 0.6.19 to 0.6.22 - [Release notes](https://github.com/ash-project/igniter/releases) - [Changelog](https://github.com/ash-project/igniter/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/igniter/compare/v0.6.19...v0.6.22) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.31 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-version: 0.2.87 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: igniter dependency-version: 0.6.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index b0c5a864..7706f2a1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.27", "bfa227b75da2b447d1b98e16a19b2fa957fe32bef33dfe33aa93b861a532c641", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f75694b0012e4e56293e1beef1d6c20a52f2f2c9baebfa5c8f2426d25d43608e"}, - "ash_sql": {:hex, :ash_sql, "0.2.86", "9b4013010981352e295eed22dc6b09998e23a724baca07f1ff617b6bec4ba8d4", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "97fa13e87d55194f7746c5a9fa97a9693bc26cdf7825d1c6d7cdfe5f166285a4"}, + "ash": {:hex, :ash, "3.5.31", "fea1abcbb58d00d1edf65ac5bccba5d679ca80754aaac6af7877cbf9056d4462", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7e07c8ae297dd764d92dd3c478e8bbb825fc0dd14a5cce83b85d19235510a74"}, + "ash_sql": {:hex, :ash_sql, "0.2.87", "17197c643918cdaee657946a1998860402dcf53a980f7665bb81d1fa53c224e7", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f82d6bf78f08bd9040af3adc28676965421598c88866074d8b1ccca65978d774"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.19", "d87703b36890bc4278341d966a7ed8e10604a18610a4331ac10c75d1af48fff4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c2070b3fdbd238fc0a0bfbc1f125b5c0f79a1fe2f5b3c7b43cd33de696783663"}, + "igniter": {:hex, :igniter, "0.6.22", "b170fc64ae0cae54a7713cf3f96e7c96183f81d75f31746de080b27518b0f96e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d753129f693a214da32f39ba5b335f7ea6e1e87833e647b280f395b3c3742acf"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From 7f441488f0205ba82deb21a91b083168a219c280 Mon Sep 17 00:00:00 2001 From: "Pois.Nada" <46747395+horberlan@users.noreply.github.com> Date: Fri, 25 Jul 2025 13:03:22 -0300 Subject: [PATCH 1132/1215] improvement: do not create snapshots for resources that have no attributes #571 (#599) --- .../migration_generator.ex | 68 ++++++++++------- test/migration_generator_test.exs | 73 +++++++++++++++++++ 2 files changed, 116 insertions(+), 25 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 84630f7d..7222c7f4 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1983,6 +1983,17 @@ defmodule AshPostgres.MigrationGenerator do end) end + defp resource_has_meaningful_content?(snapshot) do + [ + snapshot.attributes, + snapshot.identities, + snapshot.custom_indexes, + snapshot.custom_statements, + snapshot.check_constraints + ] + |> Enum.any?(&Enum.any?/1) + end + defp do_fetch_operations(snapshot, existing_snapshot, opts, acc \\ []) defp do_fetch_operations( @@ -1996,34 +2007,41 @@ defmodule AshPostgres.MigrationGenerator do end defp do_fetch_operations(snapshot, nil, opts, acc) do - empty_snapshot = %{ - attributes: [], - identities: [], - schema: nil, - custom_indexes: [], - custom_statements: [], - check_constraints: [], - table: snapshot.table, - repo: snapshot.repo, - base_filter: nil, - empty?: true, - multitenancy: %{ - attribute: nil, - strategy: nil, - global: nil - } - } - - do_fetch_operations(snapshot, empty_snapshot, opts, [ - %Operation.CreateTable{ + if resource_has_meaningful_content?(snapshot) do + empty_snapshot = %{ + attributes: [], + identities: [], + schema: nil, + custom_indexes: [], + custom_statements: [], + check_constraints: [], table: snapshot.table, - schema: snapshot.schema, repo: snapshot.repo, - multitenancy: snapshot.multitenancy, - old_multitenancy: empty_snapshot.multitenancy + base_filter: nil, + empty?: true, + multitenancy: %{ + attribute: nil, + strategy: nil, + global: nil + } } - | acc - ]) + + do_fetch_operations(snapshot, empty_snapshot, opts, [ + %Operation.CreateTable{ + table: snapshot.table, + schema: snapshot.schema, + repo: snapshot.repo, + multitenancy: snapshot.multitenancy, + old_multitenancy: empty_snapshot.multitenancy + } + | acc + ]) + else + unless opts.quiet do + Logger.info("Skipping migration for empty resource: #{snapshot.table} (no attributes, identities, indexes, statements, or constraints)") + end + acc + end end defp do_fetch_operations(snapshot, old_snapshot, opts, acc) do diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index e284e847..18577e13 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -111,6 +111,79 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "empty resources" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + end + + test "empty resource does not generate migration files" do + defresource EmptyPost, "empty_posts" do + resource do + require_primary_key?(false) + end + + actions do + defaults([:read, :create]) + end + end + + defdomain([EmptyPost]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: false, + format: false, + auto_name: true + ) + + migration_files = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert migration_files == [] + + snapshot_files = + Path.wildcard("test_snapshots_path/**/*.json") + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert snapshot_files == [] + end + + test "resource with only primary key generates migration" do + defresource PostWithId, "posts_with_id" do + attributes do + uuid_primary_key(:id) + end + end + + defdomain([PostWithId]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: false, + format: false, + auto_name: true + ) + + migration_files = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert length(migration_files) == 1 + + snapshot_files = + Path.wildcard("test_snapshots_path/**/*.json") + |> Enum.reject(&String.contains?(&1, "extensions")) + + assert length(snapshot_files) == 1 + end + end + describe "creating initial snapshots" do setup do on_exit(fn -> From 60ab568d6747a98ad6db9ceae86c663397e54bac Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 25 Jul 2025 12:11:17 -0400 Subject: [PATCH 1133/1215] fix: ensure tenant is set on query for updates --- lib/data_layer.ex | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d527bce6..e407eaff 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3139,6 +3139,14 @@ defmodule AshPostgres.DataLayer do changeset.context ) |> pkey_filter(changeset.data) + |> then(fn query -> + if changeset.tenant do + set_tenant(resource, query, changeset.tenant) + |> elem(1) + else + query + end + end) |> then(fn query -> Map.put( query, From 10b2162dfb5d20aea0375b370c22ddfb7761cfa1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 25 Jul 2025 12:12:42 -0400 Subject: [PATCH 1134/1215] chore: mix.lock --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 7706f2a1..ba960c35 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.31", "fea1abcbb58d00d1edf65ac5bccba5d679ca80754aaac6af7877cbf9056d4462", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7e07c8ae297dd764d92dd3c478e8bbb825fc0dd14a5cce83b85d19235510a74"}, + "ash": {:hex, :ash, "3.5.32", "ee717c49744374be7abe8997011115a4997917535e02d36146937fb6f89c19fe", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4704f536682538ee3ae0e8f99ff3cef6af53df09ef3e7e550e202da9d5ce685c"}, "ash_sql": {:hex, :ash_sql, "0.2.87", "17197c643918cdaee657946a1998860402dcf53a980f7665bb81d1fa53c224e7", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f82d6bf78f08bd9040af3adc28676965421598c88866074d8b1ccca65978d774"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.22", "b170fc64ae0cae54a7713cf3f96e7c96183f81d75f31746de080b27518b0f96e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d753129f693a214da32f39ba5b335f7ea6e1e87833e647b280f395b3c3742acf"}, + "igniter": {:hex, :igniter, "0.6.25", "e2774a4605c2bc9fc38f689232604aea0fc925c7966ae8e928fd9ea2fa9d300c", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b1916e1e45796d5c371c7671305e81277231617eb58b1c120915aba237fbce6a"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, From 7b6bf1d595e31b29ad75cfcccc9e365146f5bb45 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 25 Jul 2025 12:13:17 -0400 Subject: [PATCH 1135/1215] chore: release version v2.6.12 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 996183c1..8b1f381a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.12](https://github.com/ash-project/ash_postgres/compare/v2.6.11...v2.6.12) (2025-07-25) + + + + +### Bug Fixes: + +* ensure tenant is set on query for updates by Zach Daniel + +### Improvements: + +* do not create snapshots for resources that have no attributes #571 (#599) by horberlan + ## [v2.6.11](https://github.com/ash-project/ash_postgres/compare/v2.6.10...v2.6.11) (2025-07-17) diff --git a/mix.exs b/mix.exs index 68afd199..da422e01 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.11" + @version "2.6.12" def project do [ From 2655ddcf6edf947ac77fb9b7b206271e386a5345 Mon Sep 17 00:00:00 2001 From: Emad Shaaban Date: Sun, 27 Jul 2025 08:26:58 +0300 Subject: [PATCH 1136/1215] fix: ensure tenant prefix is set only for resources with context multitenancy (#600) This fixes attribute multitenancy which is currently broken by this commit https://github.com/ash-project/ash_postgres/commit/60ab568d6747a98ad6db9ceae86c663397e54bac I thought updating set_tenant is the proper way to do it since it currently just adds the prefix to the query which only makes sense in context multitenancy Maybe attribute multitenancy can be handled there too? .. by adding a filter or something, but it currently seems to be handled somewhere else (not sure where) but I see the multitennacy attribute filter is added to my update queries. * chore: format --- lib/data_layer.ex | 8 ++++++-- lib/migration_generator/migration_generator.ex | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e407eaff..506e0baf 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -893,8 +893,12 @@ defmodule AshPostgres.DataLayer do end @impl true - def set_tenant(_resource, query, tenant) do - {:ok, Map.put(Ecto.Query.put_query_prefix(query, to_string(tenant)), :__tenant__, tenant)} + def set_tenant(resource, query, tenant) do + if Ash.Resource.Info.multitenancy_strategy(resource) == :context do + {:ok, Map.put(Ecto.Query.put_query_prefix(query, to_string(tenant)), :__tenant__, tenant)} + else + {:ok, query} + end end @impl true diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 7222c7f4..74e0611d 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -2037,9 +2037,12 @@ defmodule AshPostgres.MigrationGenerator do | acc ]) else - unless opts.quiet do - Logger.info("Skipping migration for empty resource: #{snapshot.table} (no attributes, identities, indexes, statements, or constraints)") + if !opts.quiet do + Logger.info( + "Skipping migration for empty resource: #{snapshot.table} (no attributes, identities, indexes, statements, or constraints)" + ) end + acc end end From 424c536aa5371f6db668f885d3e5ae6a01a0f2ce Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 27 Jul 2025 01:27:09 -0400 Subject: [PATCH 1137/1215] chore: release version v2.6.13 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1f381a..b214923a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.13](https://github.com/ash-project/ash_postgres/compare/v2.6.12...v2.6.13) (2025-07-27) + + + + +### Bug Fixes: + +* ensure tenant prefix is set only for resources with context multitenancy (#600) by Emad Shaaban + ## [v2.6.12](https://github.com/ash-project/ash_postgres/compare/v2.6.11...v2.6.12) (2025-07-25) diff --git a/mix.exs b/mix.exs index da422e01..8f83ed84 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.12" + @version "2.6.13" def project do [ From 1e271ca54ddb7968d3f889486bfd1e8be9e5efd3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 28 Jul 2025 11:28:48 -0400 Subject: [PATCH 1138/1215] fix: deduplicate identity keys fixes #602 --- lib/migration_generator/operation.ex | 2 +- mix.lock | 2 +- test/migration_generator_test.exs | 62 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 3b266494..13127d2a 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -130,7 +130,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do def index_keys(keys, all_tenants?, multitenancy) do if multitenancy.strategy == :attribute and not all_tenants? do - [multitenancy.attribute | keys] + Enum.uniq([multitenancy.attribute | keys]) else keys end diff --git a/mix.lock b/mix.lock index ba960c35..bc1b7e3e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.32", "ee717c49744374be7abe8997011115a4997917535e02d36146937fb6f89c19fe", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4704f536682538ee3ae0e8f99ff3cef6af53df09ef3e7e550e202da9d5ce685c"}, - "ash_sql": {:hex, :ash_sql, "0.2.87", "17197c643918cdaee657946a1998860402dcf53a980f7665bb81d1fa53c224e7", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f82d6bf78f08bd9040af3adc28676965421598c88866074d8b1ccca65978d774"}, + "ash_sql": {:hex, :ash_sql, "0.2.89", "ad4ad497263b586a7f3949ceea5d44620a36cb99a1ef0ff5f58f13a77d9b99ef", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bd957aee95bbdf6326fc7a9212f9a2ab87329b99ee3646c373a87bb3c9968566"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 18577e13..1e666f72 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -2793,6 +2793,68 @@ defmodule AshPostgres.MigrationGeneratorTest do end end + describe "multitenancy identity with tenant attribute" do + setup do + on_exit(fn -> + File.rm_rf!("test_snapshots_path") + File.rm_rf!("test_migration_path") + end) + end + + test "identity including tenant attribute does not duplicate columns in index" do + defresource Channel, "channels" do + postgres do + table "channels" + repo(AshPostgres.TestRepo) + end + + actions do + defaults([:create, :read, :update, :destroy]) + end + + multitenancy do + strategy(:attribute) + attribute(:project_id) + end + + identities do + identity(:unique_type_per_project, [:project_id, :type]) + end + + attributes do + uuid_primary_key(:id) + attribute(:project_id, :uuid, allow_nil?: false, public?: true) + attribute(:type, :string, allow_nil?: false, public?: true) + attribute(:name, :string, public?: true) + end + end + + defdomain([Channel]) + + AshPostgres.MigrationGenerator.generate(Domain, + snapshot_path: "test_snapshots_path", + migration_path: "test_migration_path", + quiet: true, + format: false, + auto_name: true + ) + + assert [file] = + Path.wildcard("test_migration_path/**/*_migrate_resources*.exs") + |> Enum.reject(&String.contains?(&1, "extensions")) + + file_content = File.read!(file) + + # The index should only have project_id and type, not project_id twice + assert file_content =~ + ~S{create unique_index(:channels, [:project_id, :type], name: "channels_unique_type_per_project_index")} + + # Make sure it doesn't have duplicate columns + refute file_content =~ + ~S{create unique_index(:channels, [:project_id, :project_id, :type]} + end + end + describe "decimal precision and scale" do setup do on_exit(fn -> From 1b20a9bc9939d2a39067377027e32e855785ec64 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 29 Jul 2025 17:23:58 -0400 Subject: [PATCH 1139/1215] chore: release version v2.6.14 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b214923a..a51285bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.14](https://github.com/ash-project/ash_postgres/compare/v2.6.13...v2.6.14) (2025-07-29) + + + + +### Bug Fixes: + +* deduplicate identity keys by Zach Daniel + ## [v2.6.13](https://github.com/ash-project/ash_postgres/compare/v2.6.12...v2.6.13) (2025-07-27) diff --git a/mix.exs b/mix.exs index 8f83ed84..143f43ce 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.13" + @version "2.6.14" def project do [ From 996327077edefb5b67db96f8765168acc90bb2d6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 30 Jul 2025 10:44:12 -0400 Subject: [PATCH 1140/1215] fix: always set disable_async, and remove log level config --- lib/mix/tasks/ash_postgres.install.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index a984f2d8..5bfa39f4 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -341,9 +341,8 @@ if Code.ensure_loaded?(Igniter) do Ecto.Adapters.SQL.Sandbox ) |> Igniter.Project.Config.configure_new("test.exs", otp_app, [repo, :pool_size], 10) - |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) - |> Igniter.Project.Config.configure_new("test.exs", :logger, [:level], :warning) end + |> Igniter.Project.Config.configure_new("test.exs", :ash, [:disable_async?], true) end defp setup_data_case(igniter) do From baf4e3247e28346dc61d682756fd55948e4e4b61 Mon Sep 17 00:00:00 2001 From: Anatolij Werle Date: Sun, 3 Aug 2025 19:17:22 +0200 Subject: [PATCH 1141/1215] fix: Use new attribute source in down migration (#604) closes: #582 When an attribute is renamed, alongside other changes to it (like changing default or not null constraint), the generated up migration first renames the column, then modifies it. For the generated down migration, the order of operations is reversed, so we need to use the new column name when modifying the column, before renaming it back. --- lib/migration_generator/operation.ex | 2 +- test/migration_generator_test.exs | 37 ++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 13127d2a..88525c11 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -752,7 +752,7 @@ defmodule AshPostgres.MigrationGenerator.Operation do up(%{ op | old_attribute: op.new_attribute, - new_attribute: op.old_attribute, + new_attribute: Map.put(op.old_attribute, :source, op.new_attribute.source), old_multitenancy: op.multitenancy, multitenancy: op.old_multitenancy }) diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 1e666f72..a12a09a0 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -825,8 +825,41 @@ defmodule AshPostgres.MigrationGeneratorTest do Enum.sort(Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")) |> Enum.reject(&String.contains?(&1, "extensions")) - assert File.read!(file2) =~ ~S[rename table(:posts, prefix: "example"), :title, to: :name] - assert File.read!(file2) =~ ~S[modify :title, :text, null: true, default: nil] + contents = File.read!(file2) + + [up_side, down_side] = String.split(contents, "def down", parts: 2) + + up_side_parts = + String.split(up_side, "\n", trim: true) + |> Enum.map(&String.trim/1) + + up_rename_index = + Enum.find_index(up_side_parts, fn x -> + x == ~S[rename table(:posts, prefix: "example"), :title, to: :name] + end) + + up_modify_index = + Enum.find_index(up_side_parts, fn x -> + x == ~S[modify :name, :text, null: false, default: "fred"] + end) + + assert up_rename_index < up_modify_index + + down_side_parts = + String.split(down_side, "\n", trim: true) + |> Enum.map(&String.trim/1) + + down_modify_index = + Enum.find_index(down_side_parts, fn x -> + x == ~S[modify :name, :text, null: true, default: nil] + end) + + down_rename_index = + Enum.find_index(down_side_parts, fn x -> + x == ~S[rename table(:posts, prefix: "example"), :name, to: :title] + end) + + assert down_modify_index < down_rename_index end end From e19918967b7ea33f31d5a07a8a78f87c0ca5a772 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 6 Aug 2025 22:23:30 -0400 Subject: [PATCH 1142/1215] chore: release version v2.6.15 --- CHANGELOG.md | 11 +++++++++++ mix.exs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a51285bc..a9610b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.15](https://github.com/ash-project/ash_postgres/compare/v2.6.14...v2.6.15) (2025-08-07) + + + + +### Bug Fixes: + +* Use new attribute source in down migration (#604) by Anatolij Werle + +* always set disable_async, and remove log level config by Zach Daniel + ## [v2.6.14](https://github.com/ash-project/ash_postgres/compare/v2.6.13...v2.6.14) (2025-07-29) diff --git a/mix.exs b/mix.exs index 143f43ce..549fbcd9 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.14" + @version "2.6.15" def project do [ From 7cd3019b543b528aa75a07d718cdd36f668f0bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Kostre=C5=A1evi=C4=87?= Date: Mon, 11 Aug 2025 15:53:36 +0200 Subject: [PATCH 1143/1215] test: Add distinct sort tests (#605) * Add support for sorting with filtered relationship tests * Add filter relationship tests --- .../test_repo/post_tags/20250810102512.json | 100 ++++++++++++++++++ .../test_repo/tags/20250810102512.json | 43 ++++++++ .../20250810102512_migrate_resources57.exs | 58 ++++++++++ test/calculation_test.exs | 33 +++++- test/sort_test.exs | 24 ++++- test/support/domain.ex | 2 + test/support/resources/post.ex | 6 ++ test/support/resources/post_tag.ex | 37 +++++++ test/support/resources/tag.ex | 47 ++++++++ 9 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/post_tags/20250810102512.json create mode 100644 priv/resource_snapshots/test_repo/tags/20250810102512.json create mode 100644 priv/test_repo/migrations/20250810102512_migrate_resources57.exs create mode 100644 test/support/resources/post_tag.ex create mode 100644 test/support/resources/tag.ex diff --git a/priv/resource_snapshots/test_repo/post_tags/20250810102512.json b/priv/resource_snapshots/test_repo/post_tags/20250810102512.json new file mode 100644 index 00000000..31adb69f --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_tags/20250810102512.json @@ -0,0 +1,100 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "post_tags_post_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "scale": null, + "size": null, + "source": "post_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "post_tags_tag_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "tags" + }, + "scale": null, + "size": null, + "source": "tag_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "BC5024D4E17EDD4ABDD0EF3D81EE5932FB63DF036E1BA81989D21A12496FE4A9", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "post_tags_unique_post_tag_index", + "keys": [ + { + "type": "atom", + "value": "post_id" + }, + { + "type": "atom", + "value": "tag_id" + } + ], + "name": "unique_post_tag", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "post_tags" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/tags/20250810102512.json b/priv/resource_snapshots/test_repo/tags/20250810102512.json new file mode 100644 index 00000000..4ca1e30a --- /dev/null +++ b/priv/resource_snapshots/test_repo/tags/20250810102512.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "0", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "importance", + "type": "bigint" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "9770C678D127B7ECA9FEAF174305101CC111417744E5D5712CD7F0A865D9877A", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "tags" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250810102512_migrate_resources57.exs b/priv/test_repo/migrations/20250810102512_migrate_resources57.exs new file mode 100644 index 00000000..1eb6911a --- /dev/null +++ b/priv/test_repo/migrations/20250810102512_migrate_resources57.exs @@ -0,0 +1,58 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources57 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 + create table(:tags, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:importance, :bigint, null: false, default: 0) + end + + create table(:post_tags, primary_key: false) do + add( + :post_id, + references(:posts, + column: :id, + name: "post_tags_post_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + + add( + :tag_id, + references(:tags, + column: :id, + name: "post_tags_tag_id_fkey", + type: :uuid, + prefix: "public" + ), + primary_key: true, + null: false + ) + end + + create(unique_index(:post_tags, [:post_id, :tag_id], name: "post_tags_unique_post_tag_index")) + end + + def down do + drop_if_exists( + unique_index(:post_tags, [:post_id, :tag_id], name: "post_tags_unique_post_tag_index") + ) + + drop(constraint(:post_tags, "post_tags_post_id_fkey")) + + drop(constraint(:post_tags, "post_tags_tag_id_fkey")) + + drop(table(:post_tags)) + + drop(table(:tags)) + end +end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 287ccf9a..68c975d9 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,7 +1,19 @@ defmodule AshPostgres.CalculationTest do alias AshPostgres.Test.RecordTempEntity use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Account, Author, Comedian, Comment, Post, Record, TempEntity, User} + + alias AshPostgres.Test.{ + Account, + Author, + Comedian, + Comment, + Post, + PostTag, + Record, + Tag, + TempEntity, + User + } require Ash.Query import Ash.Expr @@ -1045,4 +1057,23 @@ defmodule AshPostgres.CalculationTest do |> Ash.Query.sort("posts_with_matching_title.relevance_score") |> Ash.read!() end + + test "sorting with filtered relationship by calculated field" do + tag = Ash.Changeset.for_create(Tag, :create) |> Ash.create!() + scores = [0, 3, 111, 22, 9, 4, 2, 33, 10] + + scores + |> Enum.each(fn score -> + post = + Ash.Changeset.for_create(Post, :create, %{score: score}) + |> Ash.create!() + + Ash.Changeset.for_create(PostTag, :create, post_id: post.id, tag_id: tag.id) + |> Ash.create!() + end) + + post_with_highest_score = Ash.load!(tag, :post_with_highest_score).post_with_highest_score + highest_score = hd(Enum.sort(scores, :desc)) + assert post_with_highest_score.score == highest_score + end end diff --git a/test/sort_test.exs b/test/sort_test.exs index 5ca9c922..cd5deddb 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.SortTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Comment, Post, PostLink, PostView} + alias AshPostgres.Test.{Comment, Post, PostLink, PostTag, PostView, Tag} require Ash.Query require Ash.Sort @@ -267,4 +267,26 @@ defmodule AshPostgres.SortTest do |> Ash.Query.sort({Ash.Sort.expr_sort(views.time, :datetime), :desc}, title: :asc) ) end + + test "sorting with filtered relationship" do + tag = Ash.Changeset.for_create(Tag, :create) |> Ash.create!() + dates = [~D[2025-01-03], ~D[2025-01-05], ~D[2025-01-01], ~D[2025-01-10], ~D[2025-01-02]] + + dates + |> Enum.each(fn date -> + {:ok, datetime} = DateTime.new(date, ~T[00:00:00]) + + post = + Ash.Changeset.for_create(Post, :create, %{created_at: datetime}) + |> Ash.create!() + + Ash.Changeset.for_create(PostTag, :create, post_id: post.id, tag_id: tag.id) + |> Ash.create!() + end) + + latest_post = Ash.load!(tag, :latest_post).latest_post + expected_date = hd(Enum.sort(dates, :desc)) + + assert DateTime.to_date(latest_post.created_at) == expected_date + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index c8c596d5..58074271 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -42,6 +42,8 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.CSV) resource(AshPostgres.Test.StandupClub) resource(AshPostgres.Test.Punchline) + resource(AshPostgres.Test.Tag) + resource(AshPostgres.Test.PostTag) end authorization do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 79eddd34..1f90535c 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -777,6 +777,12 @@ defmodule AshPostgres.Test.Post do sort: [Ash.Sort.expr_sort(parent(post_followers.order), :integer)] ) + many_to_many :tags, AshPostgres.Test.Tag do + public?(true) + through(AshPostgres.Test.PostTag) + sort(importance: :desc) + end + has_many(:views, AshPostgres.Test.PostView) do public?(true) end diff --git a/test/support/resources/post_tag.ex b/test/support/resources/post_tag.ex new file mode 100644 index 00000000..c1cb13e8 --- /dev/null +++ b/test/support/resources/post_tag.ex @@ -0,0 +1,37 @@ +defmodule AshPostgres.Test.PostTag do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + require Ash.Sort + + postgres do + table "post_tags" + repo AshPostgres.TestRepo + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + identities do + identity(:unique_post_tag, [:post_id, :tag_id]) + end + + relationships do + belongs_to :post, AshPostgres.Test.Post do + primary_key?(true) + public?(true) + allow_nil?(false) + end + + belongs_to :tag, AshPostgres.Test.Tag do + primary_key?(true) + public?(true) + allow_nil?(false) + end + end +end diff --git a/test/support/resources/tag.ex b/test/support/resources/tag.ex new file mode 100644 index 00000000..20d8d5a8 --- /dev/null +++ b/test/support/resources/tag.ex @@ -0,0 +1,47 @@ +defmodule AshPostgres.Test.Tag do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + require Ash.Sort + + postgres do + table "tags" + repo AshPostgres.TestRepo + end + + actions do + defaults([:read]) + + create :create do + accept(:*) + end + end + + attributes do + uuid_primary_key(:id) + attribute(:importance, :integer, allow_nil?: false, default: 0, public?: true) + end + + relationships do + many_to_many :posts, AshPostgres.Test.Post do + through(AshPostgres.Test.PostTag) + public?(true) + end + + has_one :latest_post, AshPostgres.Test.Post do + public?(true) + no_attributes?(true) + filter(expr(tags.id == parent(id))) + sort(created_at: :desc) + end + + has_one :post_with_highest_score, AshPostgres.Test.Post do + public?(true) + no_attributes?(true) + filter(expr(tags.id == parent(id))) + sort(score_after_winning: :desc) + end + end +end From d1236799194d4d2d4f1f5f655cc173f2c5a21d27 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 11 Aug 2025 17:39:35 -0400 Subject: [PATCH 1144/1215] improvement: Unrelated aggregates (#606) --- lib/data_layer.ex | 1 + mix.exs | 4 +- mix.lock | 8 +- .../unrelated_profiles/20250731124648.json | 91 ++++ .../unrelated_reports/20250731124648.json | 79 +++ .../20250731124648.json | 91 ++++ .../unrelated_users/20250731124648.json | 79 +++ .../20250731124648_migrate_resources57.exs | 59 ++ test/support/domain.ex | 4 + test/support/unrelated_aggregates/profile.ex | 36 ++ test/support/unrelated_aggregates/report.ex | 33 ++ .../unrelated_aggregates/secure_profile.ex | 38 ++ test/support/unrelated_aggregates/user.ex | 154 ++++++ test/unrelated_aggregates_test.exs | 507 ++++++++++++++++++ 14 files changed, 1178 insertions(+), 6 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json create mode 100644 priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json create mode 100644 priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json create mode 100644 priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json create mode 100644 priv/test_repo/migrations/20250731124648_migrate_resources57.exs create mode 100644 test/support/unrelated_aggregates/profile.ex create mode 100644 test/support/unrelated_aggregates/report.ex create mode 100644 test/support/unrelated_aggregates/secure_profile.ex create mode 100644 test/support/unrelated_aggregates/user.ex create mode 100644 test/unrelated_aggregates_test.exs diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 506e0baf..3fa1aa30 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -721,6 +721,7 @@ defmodule AshPostgres.DataLayer do when type in [:count, :sum, :first, :list, :avg, :max, :min, :exists, :custom], do: true + def can?(_, {:aggregate, :unrelated}), do: true def can?(_, :aggregate_filter), do: true def can?(_, :aggregate_sort), do: true def can?(_, :calculate), do: true diff --git a/mix.exs b/mix.exs index 549fbcd9..0ff8df8e 100644 --- a/mix.exs +++ b/mix.exs @@ -166,8 +166,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.5 and >= 3.5.13")}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.72")}, + {:ash, ash_version(github: "ash-project/ash", branch: "main")}, + {:ash_sql, ash_sql_version(github: "ash-project/ash_sql", branch: "main")}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index bc1b7e3e..3c6f7826 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.32", "ee717c49744374be7abe8997011115a4997917535e02d36146937fb6f89c19fe", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4704f536682538ee3ae0e8f99ff3cef6af53df09ef3e7e550e202da9d5ce685c"}, - "ash_sql": {:hex, :ash_sql, "0.2.89", "ad4ad497263b586a7f3949ceea5d44620a36cb99a1ef0ff5f58f13a77d9b99ef", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "bd957aee95bbdf6326fc7a9212f9a2ab87329b99ee3646c373a87bb3c9968566"}, + "ash": {:git, "https://github.com/ash-project/ash.git", "471274d2f75a4bda6405c5630b72f9323d572260", [branch: "main"]}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "40c9bcb905603dbcee2bc064f37c6f5ce30da7c7", [branch: "main"]}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.25", "e2774a4605c2bc9fc38f689232604aea0fc925c7966ae8e928fd9ea2fa9d300c", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b1916e1e45796d5c371c7671305e81277231617eb58b1c120915aba237fbce6a"}, + "igniter": {:hex, :igniter, "0.6.26", "a6b4f6680a7e158bd13cd3b2be047102e42c046b3b240578d68d89d1a39a83fa", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a4f8c404fc4cbc05a1b536c8125ae64909e3a02d5f972ffe6a3a2ebd75530f3c"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -37,7 +37,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, - "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, + "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, "reactor": {:hex, :reactor, "0.15.6", "d717f9add549b25a089a94c90197718d2d838e35d81dd776b1d81587d4cf2aaa", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "74db98165e3644d86e0f723672d91ceca4339eaa935bcad7e78bf146a46d77b9"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, diff --git a/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json b/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json new file mode 100644 index 00000000..a3c884ee --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json @@ -0,0 +1,91 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "age", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "bio", + "type": "text" + }, + { + "allow_nil?": true, + "default": "true", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "active", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "owner_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "B32650B5196D79814F5D5EF0481C757CDE5F7545E787EA911A13B9B9CBD38E7E", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "unrelated_profiles" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json b/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json new file mode 100644 index 00000000..327cce49 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json @@ -0,0 +1,79 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "title", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "author_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "score", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "728B41F3FC4BC58102261057925992766C0A3D9ED3AD7D16B887CCF8EF6B6E19", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "unrelated_reports" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json b/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json new file mode 100644 index 00000000..9df541b0 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json @@ -0,0 +1,91 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "age", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "true", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "active", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "owner_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "department", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "56BB9374A010E8E23144743DEDD10B6557BCD002338D1B06A35431AB0320F88B", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "unrelated_secure_profiles" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json b/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json new file mode 100644 index 00000000..4d83b926 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json @@ -0,0 +1,79 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "age", + "type": "bigint" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "email", + "type": "text" + }, + { + "allow_nil?": true, + "default": "\"user\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "role", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "E56D245A9DA955A309FDF1BD11C215F3056FF9BA7A4D4D3A3D5E285F0D183AC2", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "unrelated_users" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250731124648_migrate_resources57.exs b/priv/test_repo/migrations/20250731124648_migrate_resources57.exs new file mode 100644 index 00000000..3493fd6b --- /dev/null +++ b/priv/test_repo/migrations/20250731124648_migrate_resources57.exs @@ -0,0 +1,59 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources57 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 + create table(:unrelated_reports, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:title, :text) + add(:author_name, :text) + add(:score, :bigint) + + add(:inserted_at, :utc_datetime, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + ) + end + + create table(:unrelated_users, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text) + add(:age, :bigint) + add(:email, :text) + add(:role, :text, default: "user") + end + + create table(:unrelated_profiles, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text) + add(:age, :bigint) + add(:bio, :text) + add(:active, :boolean, default: true) + add(:owner_id, :uuid) + end + + create table(:unrelated_secure_profiles, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text) + add(:age, :bigint) + add(:active, :boolean, default: true) + add(:owner_id, :uuid) + add(:department, :text) + end + end + + def down do + drop(table(:unrelated_secure_profiles)) + + drop(table(:unrelated_profiles)) + + drop(table(:unrelated_users)) + + drop(table(:unrelated_reports)) + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index 58074271..58802a18 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -44,6 +44,10 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Punchline) resource(AshPostgres.Test.Tag) resource(AshPostgres.Test.PostTag) + resource(AshPostgres.Test.UnrelatedAggregatesTest.Profile) + resource(AshPostgres.Test.UnrelatedAggregatesTest.SecureProfile) + resource(AshPostgres.Test.UnrelatedAggregatesTest.Report) + resource(AshPostgres.Test.UnrelatedAggregatesTest.User) end authorization do diff --git a/test/support/unrelated_aggregates/profile.ex b/test/support/unrelated_aggregates/profile.ex new file mode 100644 index 00000000..87a9fa9f --- /dev/null +++ b/test/support/unrelated_aggregates/profile.ex @@ -0,0 +1,36 @@ +defmodule AshPostgres.Test.UnrelatedAggregatesTest.Profile do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + postgres do + table("unrelated_profiles") + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + attribute(:age, :integer, public?: true) + attribute(:bio, :string, public?: true) + attribute(:active, :boolean, default: true, public?: true) + attribute(:owner_id, :uuid, public?: true) + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + policies do + # Allow unrestricted access for most tests, but we'll create a SecureProfile for auth tests + policy action_type([:create, :update, :destroy]) do + authorize_if(always()) + end + + policy action_type(:read) do + authorize_if(always()) + end + end +end diff --git a/test/support/unrelated_aggregates/report.ex b/test/support/unrelated_aggregates/report.ex new file mode 100644 index 00000000..652df03c --- /dev/null +++ b/test/support/unrelated_aggregates/report.ex @@ -0,0 +1,33 @@ +defmodule AshPostgres.Test.UnrelatedAggregatesTest.Report do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("unrelated_reports") + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + attribute(:title, :string, public?: true) + attribute(:author_name, :string, public?: true) + attribute(:score, :integer, public?: true) + + attribute(:inserted_at, :utc_datetime, + public?: true, + default: &DateTime.utc_now/0, + allow_nil?: false + ) + end + + actions do + defaults([:read, :destroy, update: :*]) + + create :create do + primary?(true) + accept([:title, :author_name, :score, :inserted_at]) + end + end +end diff --git a/test/support/unrelated_aggregates/secure_profile.ex b/test/support/unrelated_aggregates/secure_profile.ex new file mode 100644 index 00000000..3f50bf73 --- /dev/null +++ b/test/support/unrelated_aggregates/secure_profile.ex @@ -0,0 +1,38 @@ +defmodule AshPostgres.Test.UnrelatedAggregatesTest.SecureProfile do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer, + authorizers: [Ash.Policy.Authorizer] + + postgres do + table("unrelated_secure_profiles") + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + attribute(:age, :integer, public?: true) + attribute(:active, :boolean, default: true, public?: true) + attribute(:owner_id, :uuid, public?: true) + attribute(:department, :string, public?: true) + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + policies do + # Allow creation/updates for testing setup + policy action_type([:create, :update, :destroy]) do + authorize_if(always()) + end + + # Only allow users to see their own profiles, or admins to see all + policy action_type(:read) do + authorize_if(actor_attribute_equals(:role, :admin)) + authorize_if(expr(owner_id == ^actor(:id))) + end + end +end diff --git a/test/support/unrelated_aggregates/user.ex b/test/support/unrelated_aggregates/user.ex new file mode 100644 index 00000000..ce143489 --- /dev/null +++ b/test/support/unrelated_aggregates/user.ex @@ -0,0 +1,154 @@ +defmodule AshPostgres.Test.UnrelatedAggregatesTest.User do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + alias AshPostgres.Test.UnrelatedAggregatesTest.{Profile, Report, SecureProfile} + + postgres do + table("unrelated_users") + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string, public?: true) + attribute(:age, :integer, public?: true) + attribute(:email, :string, public?: true) + attribute(:role, :atom, public?: true, default: :user) + end + + # Test basic unrelated aggregates + aggregates do + # Count of profiles with matching name + count :matching_name_profiles_count, Profile do + filter(expr(name == parent(name))) + public?(true) + end + + # Count of all active profiles (no parent filter) + count :total_active_profiles, Profile do + filter(expr(active == true)) + public?(true) + end + + # First report with matching author name + first :latest_authored_report, Report, :title do + filter(expr(author_name == parent(name))) + sort(inserted_at: :desc) + public?(true) + end + + # Sum of report scores for matching author + sum :total_report_score, Report, :score do + filter(expr(author_name == parent(name))) + public?(true) + end + + # Exists check for profiles with same name + exists :has_matching_name_profile, Profile do + filter(expr(name == parent(name))) + public?(true) + end + + # List of all profile names with same name (should be just one usually) + list :matching_profile_names, Profile, :name do + filter(expr(name == parent(name))) + public?(true) + end + + # Max age of profiles with same name + max :max_age_same_name, Profile, :age do + filter(expr(name == parent(name))) + public?(true) + end + + # Min age of profiles with same name + min :min_age_same_name, Profile, :age do + filter(expr(name == parent(name))) + public?(true) + end + + # Average age of profiles with same name + avg :avg_age_same_name, Profile, :age do + filter(expr(name == parent(name))) + public?(true) + end + + # Secure aggregate - should respect authorization policies + count :secure_profile_count, SecureProfile do + filter(expr(name == parent(name))) + public?(true) + end + end + + # Test unrelated aggregates in calculations + calculations do + calculate :matching_profiles_summary, + :string, + expr("Found " <> type(matching_name_profiles_count, :string) <> " profiles") do + public?(true) + end + + calculate :inline_profile_count, + :integer, + expr(count(Profile, filter: expr(name == parent(name)))) do + public?(true) + end + + calculate :inline_latest_report_title, + :string, + expr( + first(Report, + field: :title, + query: [ + filter: expr(author_name == parent(name)), + sort: [inserted_at: :desc] + ] + ) + ) do + public?(true) + end + + calculate :inline_total_score, + :integer, + expr( + sum(Report, + field: :score, + query: [ + filter: expr(author_name == parent(name)) + ] + ) + ) do + public?(true) + end + + calculate :complex_calculation, + :map, + expr(%{ + profile_count: count(Profile, filter: expr(name == parent(name))), + latest_report: + first(Report, + field: :title, + query: [ + filter: expr(author_name == parent(name)), + sort: [inserted_at: :desc] + ] + ), + total_score: + sum(Report, + field: :score, + query: [ + filter: expr(author_name == parent(name)) + ] + ) + }) do + public?(true) + end + end + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end +end diff --git a/test/unrelated_aggregates_test.exs b/test/unrelated_aggregates_test.exs new file mode 100644 index 00000000..09a15740 --- /dev/null +++ b/test/unrelated_aggregates_test.exs @@ -0,0 +1,507 @@ +defmodule AshPostgres.Test.UnrelatedAggregatesTest do + @moduledoc false + use AshPostgres.RepoCase, async: false + + require Ash.Query + import Ash.Expr + + alias AshPostgres.Test.UnrelatedAggregatesTest.{Profile, Report, SecureProfile, User} + + describe "basic unrelated aggregate definitions" do + test "aggregates are properly defined with related?: false" do + aggregates = Ash.Resource.Info.aggregates(User) + + count_agg = Enum.find(aggregates, &(&1.name == :matching_name_profiles_count)) + assert count_agg + assert count_agg.related? == false + assert count_agg.resource == Profile + assert count_agg.kind == :count + assert count_agg.relationship_path == [] + + first_agg = Enum.find(aggregates, &(&1.name == :latest_authored_report)) + assert first_agg + assert first_agg.related? == false + assert first_agg.resource == Report + assert first_agg.kind == :first + assert first_agg.field == :title + + sum_agg = Enum.find(aggregates, &(&1.name == :total_report_score)) + assert sum_agg + assert sum_agg.related? == false + assert sum_agg.resource == Report + assert sum_agg.kind == :sum + assert sum_agg.field == :score + end + + test "unrelated aggregates support all aggregate kinds" do + aggregates = Ash.Resource.Info.aggregates(User) + aggregate_names = Enum.map(aggregates, & &1.name) + + # Verify all kinds are supported + # count + assert :matching_name_profiles_count in aggregate_names + # first + assert :latest_authored_report in aggregate_names + # sum + assert :total_report_score in aggregate_names + # exists + assert :has_matching_name_profile in aggregate_names + # list + assert :matching_profile_names in aggregate_names + # max + assert :max_age_same_name in aggregate_names + # min + assert :min_age_same_name in aggregate_names + # avg + assert :avg_age_same_name in aggregate_names + end + + test "can define aggregates without parent filters" do + aggregates = Ash.Resource.Info.aggregates(User) + total_active_agg = Enum.find(aggregates, &(&1.name == :total_active_profiles)) + + assert total_active_agg + assert total_active_agg.related? == false + assert total_active_agg.resource == Profile + # Should have filter but no parent() reference + end + end + + describe "loading unrelated aggregates" do + setup do + # Create test data + {:ok, user1} = Ash.create(User, %{name: "John", email: "john@example.com"}) + {:ok, user2} = Ash.create(User, %{name: "Jane", email: "jane@example.com"}) + + {:ok, _profile1} = Ash.create(Profile, %{name: "John", age: 25, active: true}) + {:ok, _profile2} = Ash.create(Profile, %{name: "John", age: 30, active: true}) + {:ok, _profile3} = Ash.create(Profile, %{name: "Jane", age: 28, active: true}) + {:ok, _profile4} = Ash.create(Profile, %{name: "Bob", age: 35, active: false}) + + base_time = ~U[2024-01-01 12:00:00Z] + + {:ok, _report1} = + Ash.create(Report, %{ + title: "John's First Report", + author_name: "John", + score: 85, + inserted_at: base_time + }) + + {:ok, _report2} = + Ash.create(Report, %{ + title: "John's Latest Report", + author_name: "John", + score: 92, + inserted_at: DateTime.add(base_time, 3600, :second) + }) + + {:ok, _report3} = + Ash.create(Report, %{ + title: "Jane's Report", + author_name: "Jane", + score: 78 + }) + + %{user1: user1, user2: user2} + end + + test "can load count unrelated aggregates", %{user1: user1, user2: user2} do + # Load users with aggregates + users = + User + |> Ash.Query.load([:matching_name_profiles_count, :total_active_profiles]) + |> Ash.read!() + + john = Enum.find(users, &(&1.id == user1.id)) + jane = Enum.find(users, &(&1.id == user2.id)) + + # John should have 2 matching profiles + assert john.matching_name_profiles_count == 2 + # Both should see 3 total active profiles (John x2, Jane x1) + assert john.total_active_profiles == 3 + + # Jane should have 1 matching profile + assert jane.matching_name_profiles_count == 1 + assert jane.total_active_profiles == 3 + end + + test "can load first unrelated aggregates", %{user1: user1} do + user = + User + |> Ash.Query.filter(id == ^user1.id) + |> Ash.Query.load(:latest_authored_report) + |> Ash.read_one!() + + # Should get the latest report title + assert user.latest_authored_report == "John's Latest Report" + end + + test "can load sum unrelated aggregates", %{user1: user1, user2: user2} do + users = + User + |> Ash.Query.load(:total_report_score) + |> Ash.read!() + + john = Enum.find(users, &(&1.id == user1.id)) + jane = Enum.find(users, &(&1.id == user2.id)) + + # John's total score: 85 + 92 = 177 + assert john.total_report_score == 177 + # Jane's total score: 78 + assert jane.total_report_score == 78 + end + + test "can load exists unrelated aggregates", %{user1: user1} do + user = + User + |> Ash.Query.filter(id == ^user1.id) + |> Ash.Query.load(:has_matching_name_profile) + |> Ash.read_one!() + + assert user.has_matching_name_profile == true + end + + test "can load list unrelated aggregates", %{user1: user1} do + user = + User + |> Ash.Query.filter(id == ^user1.id) + |> Ash.Query.load(:matching_profile_names) + |> Ash.read_one!() + + # Should have two "John" entries + assert length(user.matching_profile_names) == 2 + assert Enum.all?(user.matching_profile_names, &(&1 == "John")) + end + + test "can load min/max/avg unrelated aggregates", %{user1: user1} do + user = + User + |> Ash.Query.filter(id == ^user1.id) + |> Ash.Query.load([:min_age_same_name, :max_age_same_name, :avg_age_same_name]) + |> Ash.read_one!() + + # John profiles have ages 25 and 30 + assert user.min_age_same_name == 25 + assert user.max_age_same_name == 30 + assert user.avg_age_same_name == 27.5 + end + end + + describe "unrelated aggregates in calculations" do + setup do + {:ok, user} = Ash.create(User, %{name: "Alice", email: "alice@example.com"}) + {:ok, _profile} = Ash.create(Profile, %{name: "Alice", age: 25, active: true}) + + {:ok, _report} = + Ash.create(Report, %{ + title: "Alice's Research", + author_name: "Alice", + score: 95, + inserted_at: ~U[2024-01-01 12:00:00Z] + }) + + %{user: user} + end + + test "calculations using named unrelated aggregates work", %{user: user} do + user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load(:matching_profiles_summary) + |> Ash.read_one!() + + assert user.matching_profiles_summary == "Found 1 profiles" + end + + test "inline unrelated aggregates in calculations work", %{user: user} do + user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load([ + :inline_profile_count, + :inline_latest_report_title, + :inline_total_score + ]) + |> Ash.read_one!() + + assert user.inline_profile_count == 1 + assert user.inline_latest_report_title == "Alice's Research" + assert user.inline_total_score == 95 + end + + test "complex calculations with multiple inline unrelated aggregates work", %{user: user} do + user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load(:complex_calculation) + |> Ash.read_one!() + + assert user.complex_calculation == %{ + profile_count: 1, + latest_report: "Alice's Research", + total_score: 95 + } + end + end + + describe "data layer capability checking" do + test "Postgres data layer should support unrelated aggregates" do + # This will fail until we implement the capability + assert AshPostgres.DataLayer.can?(nil, {:aggregate, :unrelated}) == true + end + + test "error when data layer doesn't support unrelated aggregates" do + # Test with a mock data layer that doesn't support unrelated aggregates + # This will be relevant when we add the capability checking + end + end + + describe "authorization with unrelated aggregates" do + # These tests verify that authorization works properly for unrelated aggregates + # The main concern is that unrelated aggregates don't have relationship paths, + # so the authorization logic must handle this correctly + + test "unrelated aggregates work without relationship path authorization errors" do + # This test verifies that unrelated aggregates don't trigger the + # :lists.droplast([]) error that was happening before the fix + {:ok, user} = Ash.create(User, %{name: "AuthTest", email: "auth@example.com"}) + {:ok, _profile} = Ash.create(Profile, %{name: "AuthTest", age: 25, active: true}) + + # This should not raise authorization errors + user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load(:matching_name_profiles_count) + |> Ash.read_one!() + + assert user.matching_name_profiles_count == 1 + end + + test "unrelated aggregates in calculations don't cause authorization errors" do + # Test that the authorization logic correctly handles unrelated aggregates + # when they're referenced in calculations + {:ok, user} = Ash.create(User, %{name: "CalcAuth", email: "calcauth@example.com"}) + {:ok, _profile} = Ash.create(Profile, %{name: "CalcAuth", age: 30, active: true}) + + # This should not raise authorization errors + user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load(:matching_profiles_summary) + |> Ash.read_one!() + + assert user.matching_profiles_summary == "Found 1 profiles" + end + + test "multiple unrelated aggregates can be loaded together without authorization issues" do + # Test loading multiple unrelated aggregates simultaneously + {:ok, user} = Ash.create(User, %{name: "MultiAuth", email: "multi@example.com"}) + {:ok, _profile} = Ash.create(Profile, %{name: "MultiAuth", age: 28, active: true}) + + {:ok, _report} = + Ash.create(Report, %{ + title: "MultiAuth Report", + author_name: "MultiAuth", + score: 88, + inserted_at: ~U[2024-01-01 15:00:00Z] + }) + + # Loading multiple unrelated aggregates should work + user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load([ + :matching_name_profiles_count, + :total_active_profiles, + :latest_authored_report, + :total_report_score + ]) + |> Ash.read_one!() + + assert user.matching_name_profiles_count == 1 + # Could include profiles from other tests + assert user.total_active_profiles >= 1 + assert user.latest_authored_report == "MultiAuth Report" + assert user.total_report_score == 88 + end + + test "unrelated aggregates respect target resource authorization policies" do + admin_user = Ash.create!(User, %{name: "Admin", email: "admin@test.com", role: :admin}) + regular_user1 = Ash.create!(User, %{name: "User1", email: "user1@test.com", role: :user}) + regular_user2 = Ash.create!(User, %{name: "User1", email: "user2@test.com", role: :user}) + + Ash.create!(SecureProfile, %{ + name: "User1", + age: 25, + active: true, + owner_id: regular_user1.id, + department: "Engineering" + }) + + Ash.create!(SecureProfile, %{ + name: "User1", + age: 30, + active: true, + owner_id: regular_user2.id, + department: "Marketing" + }) + + Ash.create!(SecureProfile, %{ + name: "Admin", + age: 35, + active: true, + owner_id: admin_user.id, + department: "Management" + }) + + user1_result = + User + |> Ash.Query.filter(id == ^regular_user1.id) + |> Ash.Query.load(:secure_profile_count) + |> Ash.read_one!(actor: regular_user1, authorize?: true) + + assert user1_result.secure_profile_count == 1 + + user2_result = + User + |> Ash.Query.filter(id == ^regular_user2.id) + |> Ash.Query.load(:secure_profile_count) + |> Ash.read_one!(actor: regular_user2, authorize?: true) + + assert user2_result.secure_profile_count == 1 + + admin_as_user1 = + User + |> Ash.Query.filter(id == ^regular_user1.id) + |> Ash.Query.load(:secure_profile_count) + |> Ash.read_one!(actor: admin_user, authorize?: true) + + assert admin_as_user1.secure_profile_count == 2 + + admin_result = + User + |> Ash.Query.filter(id == ^admin_user.id) + |> Ash.Query.load(:secure_profile_count) + |> Ash.read_one!(actor: admin_user, authorize?: true) + + assert admin_result.secure_profile_count == 1 + end + end + + describe "edge cases" do + test "unrelated aggregates work with empty result sets" do + users = + User + |> Ash.Query.filter(name == "NonExistent") + |> Ash.Query.load(:matching_name_profiles_count) + |> Ash.read!() + + # Should be empty, but aggregate should still work + assert users == [] + end + + test "unrelated aggregates work with filters that return no results" do + {:ok, user} = Ash.create(User, %{name: "Unique", email: "unique@example.com"}) + + # No profiles with name "Unique" exist + loaded_user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.load(:matching_name_profiles_count) + |> Ash.read_one!() + + assert loaded_user.matching_name_profiles_count == 0 + end + + test "unrelated aggregates work with complex filter expressions" do + {:ok, user} = + Ash.create(User, %{name: "ComplexTest", age: 25, email: "complex@example.com"}) + + # Create profiles with various attributes + {:ok, _profile1} = + Ash.create(Profile, %{name: "ComplexTest", age: 25, bio: "Bio contains ComplexTest"}) + + {:ok, _profile2} = + Ash.create(Profile, %{name: "ComplexTest", age: 30, bio: "Different bio"}) + + {:ok, _profile3} = + Ash.create(Profile, %{name: "Other", age: 25, bio: "ComplexTest mentioned"}) + + # Test parent() with boolean AND + loaded_user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.aggregate(:same_name_and_age, :count, Profile, + query: [filter: expr(name == parent(name) and age == parent(age))] + ) + |> Ash.read_one!() + + assert loaded_user.aggregates.same_name_and_age == 1 + + # Test parent() with OR conditions + loaded_user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.aggregate(:name_or_bio_match, :count, Profile, + query: [filter: expr(name == parent(name) or contains(bio, parent(name)))] + ) + |> Ash.read_one!() + + assert loaded_user.aggregates.name_or_bio_match == 3 + + # Test parent() with comparison operators + loaded_user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.aggregate(:older_profiles, :count, Profile, + query: [filter: expr(name == parent(name) and age > parent(age))] + ) + |> Ash.read_one!() + + assert loaded_user.aggregates.older_profiles == 1 + end + + test "parent() works with nested conditional expressions" do + {:ok, user} = Ash.create(User, %{name: "NestedTest", age: 30, email: "nested@example.com"}) + + {:ok, _profile1} = Ash.create(Profile, %{name: "NestedTest", age: 25, bio: "Young"}) + {:ok, _profile2} = Ash.create(Profile, %{name: "NestedTest", age: 35, bio: "Old"}) + {:ok, _profile3} = Ash.create(Profile, %{name: "Other", age: 30, bio: "Same age"}) + + # Test nested parentheses with parent() + loaded_user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.aggregate(:complex_condition, :count, Profile, + query: [filter: expr(name == parent(name) and (age < parent(age) or age > parent(age)))] + ) + |> Ash.read_one!() + + assert loaded_user.aggregates.complex_condition == 2 + end + + test "parent() works with string functions" do + {:ok, user} = Ash.create(User, %{name: "StringTest", email: "string@example.com"}) + + {:ok, _profile1} = + Ash.create(Profile, %{name: "StringTest", bio: "StringTest is mentioned here"}) + + {:ok, _profile2} = + Ash.create(Profile, %{name: "DifferentName", bio: "StringTest appears in bio"}) + + {:ok, _profile3} = Ash.create(Profile, %{name: "StringTest", bio: "No mention"}) + + # Test parent() with string contains function + loaded_user = + User + |> Ash.Query.filter(id == ^user.id) + |> Ash.Query.aggregate(:bio_mentions_name, :count, Profile, + query: [filter: expr(contains(bio, parent(name)))] + ) + |> Ash.read_one!() + + assert loaded_user.aggregates.bio_mentions_name == 2 + end + end +end From d83157df2ee0a27e5d7fe4450ca44a43a0d9454b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 21 Aug 2025 18:33:40 -0400 Subject: [PATCH 1145/1215] chore: update ash/ash_sql --- mix.exs | 4 ++-- mix.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 0ff8df8e..e9bd0ed8 100644 --- a/mix.exs +++ b/mix.exs @@ -166,8 +166,8 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version(github: "ash-project/ash", branch: "main")}, - {:ash_sql, ash_sql_version(github: "ash-project/ash_sql", branch: "main")}, + {:ash, ash_version("~> 3.5 and >= 3.5.35")}, + {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.90")}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index 3c6f7826..a6ba65e8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:git, "https://github.com/ash-project/ash.git", "471274d2f75a4bda6405c5630b72f9323d572260", [branch: "main"]}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "40c9bcb905603dbcee2bc064f37c6f5ce30da7c7", [branch: "main"]}, + "ash": {:hex, :ash, "3.5.35", "ff63b9d3670a05ad036ecc8732c0143770aff4e23f07662931a9ebd1f4aba3c4", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6ff48f80efabcc1867af9847c8656fa376d9b800e8988b82ec0a42bee4f6b58"}, + "ash_sql": {:hex, :ash_sql, "0.2.90", "6b533e963e4b7c0855654597d8eb7bb87c30db0be7328628cb9bb63e2e547a54", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "57a7235f22f01381b1a2d63364c353fe6fa31278f3bbd402aacab6dba78a7e6b"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.26", "a6b4f6680a7e158bd13cd3b2be047102e42c046b3b240578d68d89d1a39a83fa", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a4f8c404fc4cbc05a1b536c8125ae64909e3a02d5f972ffe6a3a2ebd75530f3c"}, + "igniter": {:hex, :igniter, "0.6.28", "9db10192f19f10b924f14c805f5b2ad992617fccaff9cf9582b7f065d562d4d8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ad9369d626aeca21079ef17661a2672fb32598610c5e5bccae2537efd36b27d4"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -44,7 +44,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.67", "67626cb9f59ea4b1c5aa85d4afdd025e0740cbd49ed82665d0a40ff007d7fd4b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c8575402e3afc66871362e821bece890536d16319cdb758c5fb2d1250182e46f"}, + "spark": {:hex, :spark, "2.2.68", "4c4547c88d73311e3157bc402ab27f3a7bbd5e55a2ee92bcf7cd3a0a475d201e", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "2fa4dd89801415a098a421589633f0e3df7ed9ff4046e80a65d35a413bc0d194"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, From 3d1fe5e744c3815b5f644cece4d68c087c8346e6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 21 Aug 2025 18:49:18 -0400 Subject: [PATCH 1146/1215] chore: release version v2.6.16 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9610b52..7df2c49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.16](https://github.com/ash-project/ash_postgres/compare/v2.6.15...v2.6.16) (2025-08-21) + + + + +### Improvements: + +* Unrelated aggregates (#606) by Zach Daniel + ## [v2.6.15](https://github.com/ash-project/ash_postgres/compare/v2.6.14...v2.6.15) (2025-08-07) diff --git a/mix.exs b/mix.exs index e9bd0ed8..2d3f04b1 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.15" + @version "2.6.16" def project do [ From ed22f3e699e30069a3048f5f2566d1bd45055805 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 23 Aug 2025 10:37:21 -0400 Subject: [PATCH 1147/1215] chore: rename migration file --- ...e_resources57.exs => 20250810102512_migrate_resources58.exs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename priv/test_repo/migrations/{20250810102512_migrate_resources57.exs => 20250810102512_migrate_resources58.exs} (95%) diff --git a/priv/test_repo/migrations/20250810102512_migrate_resources57.exs b/priv/test_repo/migrations/20250810102512_migrate_resources58.exs similarity index 95% rename from priv/test_repo/migrations/20250810102512_migrate_resources57.exs rename to priv/test_repo/migrations/20250810102512_migrate_resources58.exs index 1eb6911a..484e4dc0 100644 --- a/priv/test_repo/migrations/20250810102512_migrate_resources57.exs +++ b/priv/test_repo/migrations/20250810102512_migrate_resources58.exs @@ -1,4 +1,4 @@ -defmodule AshPostgres.TestRepo.Migrations.MigrateResources57 do +defmodule AshPostgres.TestRepo.Migrations.MigrateResources58 do @moduledoc """ Updates resources based on their most recent snapshots. From ad0fd663a9e3de091ef1622907c9ae1e7c557014 Mon Sep 17 00:00:00 2001 From: Sheharyar Naseer Date: Mon, 25 Aug 2025 07:06:05 -0600 Subject: [PATCH 1148/1215] fix: resolve a typo in pending dev migration error message (#608) --- lib/migration_generator/migration_generator.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 74e0611d..86920ed4 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -460,8 +460,8 @@ defmodule AshPostgres.MigrationGenerator do You have migrations remaining that were generated with the --dev flag. - Run `mix ash.codegen ` to remove the dev migraitons and replace them - with production ready migrations. + Run `mix ash.codegen ` to remove the dev migrations and replace them + with production-ready migrations. """) exit({:shutdown, 1}) From 22ab05e0b2d56600dbe72dda12a2f1402d48b9a4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 31 Aug 2025 11:48:45 -0400 Subject: [PATCH 1149/1215] chore: release version v2.6.17 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df2c49a..08169773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.17](https://github.com/ash-project/ash_postgres/compare/v2.6.16...v2.6.17) (2025-08-31) + + + + +### Bug Fixes: + +* resolve a typo in pending dev migration error message (#608) by Sheharyar Naseer + ## [v2.6.16](https://github.com/ash-project/ash_postgres/compare/v2.6.15...v2.6.16) (2025-08-21) diff --git a/mix.exs b/mix.exs index 2d3f04b1..3e8719c1 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.16" + @version "2.6.17" def project do [ From ff3402e759faae5364e37ad855a0f06a1778c304 Mon Sep 17 00:00:00 2001 From: Rodolfo Torres Date: Sun, 31 Aug 2025 16:46:44 -0400 Subject: [PATCH 1150/1215] test: test for default sort referencing parent field in many_to_many relationship with no_attributes? true (#607) --- test/parent_sort_test.ex | 32 ++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 6 ++++++ 2 files changed, 38 insertions(+) create mode 100644 test/parent_sort_test.ex diff --git a/test/parent_sort_test.ex b/test/parent_sort_test.ex new file mode 100644 index 00000000..e4647672 --- /dev/null +++ b/test/parent_sort_test.ex @@ -0,0 +1,32 @@ +defmodule AshPostgres.Test.ParentSortTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.{Organization, Post, User} + + require Ash.Query + + test "can reference parent field when declaring default sort in has_many no_attributes? relationship" do + organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "test_org"}) + |> Ash.create!() + + user = + User + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) + |> Ash.create!() + + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, title: "test_org"}) + |> Ash.create!() + + assert {:ok, _} = + Post + |> Ash.Query.load(:recommendations) + |> Ash.read(authorize?: false) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 1f90535c..0441dda9 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -614,6 +614,12 @@ defmodule AshPostgres.Test.Post do filter(expr(fragment("? = ?", title, parent(organization.name)))) end + has_many(:recommendations, __MODULE__) do + public?(true) + no_attributes?(true) + sort([calc(fragment("abs(extract epoch from ?))", parent(datetime) - datetime))]) + end + belongs_to :parent_post, __MODULE__ do public?(true) end From 7d53f476b9af782bd88cedf76b74cc7cb5d50ca9 Mon Sep 17 00:00:00 2001 From: Rodolfo Torres Date: Mon, 1 Sep 2025 08:07:07 -0400 Subject: [PATCH 1151/1215] rename test file so it matches pattern (#609) --- test/{parent_sort_test.ex => parent_sort_test.exs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{parent_sort_test.ex => parent_sort_test.exs} (100%) diff --git a/test/parent_sort_test.ex b/test/parent_sort_test.exs similarity index 100% rename from test/parent_sort_test.ex rename to test/parent_sort_test.exs From 1948f80b2a982f19981c599a953d311d8a483e4d Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Sep 2025 21:46:51 -0400 Subject: [PATCH 1152/1215] fix: annotate unrelated exists expressions as supported --- lib/data_layer.ex | 1 + test/unrelated_aggregates_test.exs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3fa1aa30..27b88dd1 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -722,6 +722,7 @@ defmodule AshPostgres.DataLayer do do: true def can?(_, {:aggregate, :unrelated}), do: true + def can?(_, {:exists, :unrelated}), do: true def can?(_, :aggregate_filter), do: true def can?(_, :aggregate_sort), do: true def can?(_, :calculate), do: true diff --git a/test/unrelated_aggregates_test.exs b/test/unrelated_aggregates_test.exs index 09a15740..0ec55535 100644 --- a/test/unrelated_aggregates_test.exs +++ b/test/unrelated_aggregates_test.exs @@ -503,5 +503,22 @@ defmodule AshPostgres.Test.UnrelatedAggregatesTest do assert loaded_user.aggregates.bio_mentions_name == 2 end + + test "unrelated exists expressions work with parent() reference" do + {:ok, user1} = Ash.create(User, %{name: "ExistsTest", email: "exists@example.com"}) + {:ok, user2} = Ash.create(User, %{name: "NoMatch", email: "nomatch@example.com"}) + + {:ok, _profile} = Ash.create(Profile, %{name: "ExistsTest", age: 25, active: true}) + + # Check if any profile exists with the same name using unrelated exists + users_with_matching_profiles = + User + |> Ash.Query.filter(exists(Profile, name == parent(name))) + |> Ash.read!() + + user_ids = Enum.map(users_with_matching_profiles, & &1.id) + assert user1.id in user_ids + refute user2.id in user_ids + end end end From 36213e4e00631a5911b9bfcd6acfd558d0fc5cbf Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 7 Sep 2025 01:15:27 +0200 Subject: [PATCH 1153/1215] fix: properly handle sorts w/ parent refs on lateral joins --- lib/data_layer.ex | 11 +++++++---- test/parent_sort_test.exs | 15 +++++++-------- test/support/resources/post.ex | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 27b88dd1..e4cde133 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1185,7 +1185,7 @@ defmodule AshPostgres.DataLayer do if query.__ash_bindings__[:__order__?] do {:ok, from(source in data_layer_query, - inner_lateral_join: destination in ^subquery, + inner_lateral_join: destination in subquery(get_subquery(subquery)), on: true, order_by: destination.__order__, select: merge(destination, %{__lateral_join_source__: map(source, ^source_pkey)}), @@ -1194,7 +1194,7 @@ defmodule AshPostgres.DataLayer do else {:ok, from(source in data_layer_query, - inner_lateral_join: destination in ^subquery, + inner_lateral_join: destination in subquery(get_subquery(subquery)), on: true, select: merge(destination, %{__lateral_join_source__: map(source, ^source_pkey)}), distinct: true @@ -1286,7 +1286,7 @@ defmodule AshPostgres.DataLayer do {:ok, from(source in data_layer_query, where: field(source, ^source_attribute) in ^source_values, - inner_lateral_join: destination in ^subquery, + inner_lateral_join: destination in subquery(get_subquery(subquery)), on: true, select: destination, select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, @@ -1321,7 +1321,7 @@ defmodule AshPostgres.DataLayer do {:ok, from(source in data_layer_query, where: field(source, ^source_attribute) in ^source_values, - inner_lateral_join: destination in ^subquery, + inner_lateral_join: destination in subquery(get_subquery(subquery)), on: true, select: destination, select_merge: %{__lateral_join_source__: map(source, ^source_pkey)}, @@ -1434,6 +1434,9 @@ defmodule AshPostgres.DataLayer do Ash.Query.do_filter(query, expr) end + defp get_subquery(%Ecto.Query{} = query), do: query + defp get_subquery(%Ecto.SubQuery{query: query}), do: query + @doc false def set_subquery_prefix(data_layer_query, source_query, resource) do repo = AshPostgres.DataLayer.Info.repo(resource, :mutate) diff --git a/test/parent_sort_test.exs b/test/parent_sort_test.exs index e4647672..423d6634 100644 --- a/test/parent_sort_test.exs +++ b/test/parent_sort_test.exs @@ -11,10 +11,9 @@ defmodule AshPostgres.Test.ParentSortTest do |> Ash.Changeset.for_create(:create, %{name: "test_org"}) |> Ash.create!() - user = - User - |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) - |> Ash.create!() + User + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) + |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) @@ -24,9 +23,9 @@ defmodule AshPostgres.Test.ParentSortTest do |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, title: "test_org"}) |> Ash.create!() - assert {:ok, _} = - Post - |> Ash.Query.load(:recommendations) - |> Ash.read(authorize?: false) + # just asserting it doesn't errror + Post + |> Ash.Query.load(:recommendations) + |> Ash.read!(authorize?: false) end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 0441dda9..dfba5a06 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -617,7 +617,7 @@ defmodule AshPostgres.Test.Post do has_many(:recommendations, __MODULE__) do public?(true) no_attributes?(true) - sort([calc(fragment("abs(extract epoch from ?))", parent(datetime) - datetime))]) + sort([calc(parent(datetime) > now())]) end belongs_to :parent_post, __MODULE__ do From 6f97884ec6930c79441f05afb4fa3a18959402d7 Mon Sep 17 00:00:00 2001 From: kernel-io Date: Mon, 8 Sep 2025 21:42:13 +1200 Subject: [PATCH 1154/1215] test: add failing tests for loads not getting added automatically in certain situations (#612) --- test/parent_filter_test.exs | 29 ++++++++++++++++++++++++++++- test/support/resources/post.ex | 10 ++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/test/parent_filter_test.exs b/test/parent_filter_test.exs index fefbde24..78d295fe 100644 --- a/test/parent_filter_test.exs +++ b/test/parent_filter_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.Test.ParentFilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Organization, Post, User} + alias AshPostgres.Test.{Organization, Post, User, Comment} require Ash.Query @@ -44,4 +44,31 @@ defmodule AshPostgres.Test.ParentFilterTest do ) |> Ash.read(authorize?: false) end + + test "something else" do + organization = + Organization + |> Ash.Changeset.for_create(:create, %{name: "test_org"}) + |> Ash.create!() + + post_in_my_org = + Post + |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, title: "test_org"}) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "test_org"}) + |> Ash.Changeset.manage_relationship(:post, post_in_my_org, type: :append_and_remove) + |> Ash.create!() + + assert {:ok, _} = + Post + |> Ash.Query.for_read(:read) + |> Ash.Query.filter( + organizations_with_posts_that_have_the_post_title_somewhere_in_their_comments.name in [ + ^organization.name + ] + ) + |> Ash.read(authorize?: false) + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index dfba5a06..e6009d57 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -614,6 +614,14 @@ defmodule AshPostgres.Test.Post do filter(expr(fragment("? = ?", title, parent(organization.name)))) end + has_many( + :organizations_with_posts_that_have_the_post_title_somewhere_in_their_comments, + AshPostgres.Test.Organization + ) do + no_attributes?(true) + filter(expr(fragment("POSITION(? IN ?) > 0", posts.title, parent(concated_comment_titles)))) + end + has_many(:recommendations, __MODULE__) do public?(true) no_attributes?(true) @@ -1020,6 +1028,8 @@ defmodule AshPostgres.Test.Post do calculate(:author_profile_description_from_agg, :string, expr(author_profile_description)) calculate(:latest_comment_title, :string, expr(latest_comment.title), allow_nil?: true) + + calculate(:concated_comment_titles, :string, expr(fragment("concat(?)", comment_titles))) end aggregates do From 89622eb6fef805278eb76724531fceea0dbe90e3 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 8 Sep 2025 11:43:37 +0200 Subject: [PATCH 1155/1215] test: add tests of distinct/sort issues --- .../test_repo/chats/20250908093505.json | 43 ++++++++ .../test_repo/customers/20250908073737.json | 43 ++++++++ .../test_repo/messages/20250908093505.json | 98 +++++++++++++++++++ .../test_repo/orders/20250908073737.json | 93 ++++++++++++++++++ .../test_repo/products/20250908073737.json | 43 ++++++++ .../20250908073737_migrate_resources59.exs | 73 ++++++++++++++ .../20250908093505_migrate_resources60.exs | 55 +++++++++++ test/distinct_test.exs | 28 +++++- test/sort_test.exs | 54 +++++++++- test/support/domain.ex | 5 + test/support/resources/chat.ex | 40 ++++++++ test/support/resources/customer.ex | 35 +++++++ test/support/resources/message.ex | 30 ++++++ test/support/resources/order.ex | 32 ++++++ test/support/resources/product.ex | 33 +++++++ 15 files changed, 703 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/chats/20250908093505.json create mode 100644 priv/resource_snapshots/test_repo/customers/20250908073737.json create mode 100644 priv/resource_snapshots/test_repo/messages/20250908093505.json create mode 100644 priv/resource_snapshots/test_repo/orders/20250908073737.json create mode 100644 priv/resource_snapshots/test_repo/products/20250908073737.json create mode 100644 priv/test_repo/migrations/20250908073737_migrate_resources59.exs create mode 100644 priv/test_repo/migrations/20250908093505_migrate_resources60.exs create mode 100644 test/support/resources/chat.ex create mode 100644 test/support/resources/customer.ex create mode 100644 test/support/resources/message.ex create mode 100644 test/support/resources/order.ex create mode 100644 test/support/resources/product.ex diff --git a/priv/resource_snapshots/test_repo/chats/20250908093505.json b/priv/resource_snapshots/test_repo/chats/20250908093505.json new file mode 100644 index 00000000..aa5ea757 --- /dev/null +++ b/priv/resource_snapshots/test_repo/chats/20250908093505.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "E200EA8628A3CC9B29EF3921CA5E72F3819E7E4CBA4B64E2E0684A2F45F9E873", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "chats" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/customers/20250908073737.json b/priv/resource_snapshots/test_repo/customers/20250908073737.json new file mode 100644 index 00000000..28f93ea2 --- /dev/null +++ b/priv/resource_snapshots/test_repo/customers/20250908073737.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "93AF0132FAB23B3B438FD526411F0C9E504C80F803322B5ABCDFFA8B3103B762", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "customers" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/messages/20250908093505.json b/priv/resource_snapshots/test_repo/messages/20250908093505.json new file mode 100644 index 00000000..abe62ca8 --- /dev/null +++ b/priv/resource_snapshots/test_repo/messages/20250908093505.json @@ -0,0 +1,98 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "content", + "type": "text" + }, + { + "allow_nil?": true, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "sent_at", + "type": "utc_datetime" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "read_at", + "type": "utc_datetime" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "messages_chat_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "chats" + }, + "scale": null, + "size": null, + "source": "chat_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "97C85779868973EAB5363B16FCE9562DBAD347062547341855A08557B127FD7E", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "messages" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/orders/20250908073737.json b/priv/resource_snapshots/test_repo/orders/20250908073737.json new file mode 100644 index 00000000..104128a4 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orders/20250908073737.json @@ -0,0 +1,93 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "orders_customer_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "customers" + }, + "scale": null, + "size": null, + "source": "customer_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "orders_product_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "products" + }, + "scale": null, + "size": null, + "source": "product_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "BDB105B45D28F7BB79E6AC102805F748C15EB6B19B7FFC6D166E71CD40F60B15", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "orders" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/products/20250908073737.json b/priv/resource_snapshots/test_repo/products/20250908073737.json new file mode 100644 index 00000000..1b787e4f --- /dev/null +++ b/priv/resource_snapshots/test_repo/products/20250908073737.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "F8C4228D8DCC1DADD126675744C0BEE52107FADA5135A2E603A26F5F115709DC", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "products" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250908073737_migrate_resources59.exs b/priv/test_repo/migrations/20250908073737_migrate_resources59.exs new file mode 100644 index 00000000..a4acd79a --- /dev/null +++ b/priv/test_repo/migrations/20250908073737_migrate_resources59.exs @@ -0,0 +1,73 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources59 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 + create table(:products, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text) + end + + create table(:orders, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:customer_id, :uuid, null: false) + add(:product_id, :uuid, null: false) + end + + create table(:customers, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:orders) do + modify( + :customer_id, + references(:customers, + column: :id, + name: "orders_customer_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + + modify( + :product_id, + references(:products, + column: :id, + name: "orders_product_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:customers) do + add(:name, :text) + end + end + + def down do + alter table(:customers) do + remove(:name) + end + + drop(constraint(:orders, "orders_customer_id_fkey")) + + drop(constraint(:orders, "orders_product_id_fkey")) + + alter table(:orders) do + modify(:product_id, :uuid) + modify(:customer_id, :uuid) + end + + drop(table(:customers)) + + drop(table(:orders)) + + drop(table(:products)) + end +end diff --git a/priv/test_repo/migrations/20250908093505_migrate_resources60.exs b/priv/test_repo/migrations/20250908093505_migrate_resources60.exs new file mode 100644 index 00000000..404906e5 --- /dev/null +++ b/priv/test_repo/migrations/20250908093505_migrate_resources60.exs @@ -0,0 +1,55 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources60 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 + create table(:messages, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:content, :text) + add(:sent_at, :utc_datetime, default: fragment("(now() AT TIME ZONE 'utc')")) + add(:read_at, :utc_datetime) + add(:chat_id, :uuid, null: false) + end + + create table(:chats, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:messages) do + modify( + :chat_id, + references(:chats, + column: :id, + name: "messages_chat_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:chats) do + add(:name, :text) + end + end + + def down do + alter table(:chats) do + remove(:name) + end + + drop(constraint(:messages, "messages_chat_id_fkey")) + + alter table(:messages) do + modify(:chat_id, :uuid) + end + + drop(table(:chats)) + + drop(table(:messages)) + end +end diff --git a/test/distinct_test.exs b/test/distinct_test.exs index bbc9a1c6..452f0068 100644 --- a/test/distinct_test.exs +++ b/test/distinct_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.DistinctTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.Post + alias AshPostgres.Test.{Customer, Post, Product, Order} require Ash.Query @@ -177,4 +177,30 @@ defmodule AshPostgres.DistinctTest do assert [_, _] = results end + + test "distinct breaks with prepare build(sort: [:id]) - original issue reproduction" do + + customer = + Customer + |> Ash.Changeset.for_create(:create, %{name: "Test Customer"}) + |> Ash.create!() + + product = + Product + |> Ash.Changeset.for_create(:create, %{name: "Test Product"}) + |> Ash.create!() + + Order + |> Ash.Changeset.for_create(:create, %{customer_id: customer.id, product_id: product.id}) + |> Ash.create!() + + Order + |> Ash.Changeset.for_create(:create, %{customer_id: customer.id, product_id: product.id}) + |> Ash.create!() + + customer_with_products = Ash.load!(customer, :purchased_products) + purchased_products = customer_with_products.purchased_products + + assert length(purchased_products) == 1, "Expected 1 unique product, got #{length(purchased_products)} products. This indicates the distinct + sort preparation bug." + end end diff --git a/test/sort_test.exs b/test/sort_test.exs index cd5deddb..6be3113d 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -1,10 +1,11 @@ defmodule AshPostgres.SortTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Comment, Post, PostLink, PostTag, PostView, Tag} + alias AshPostgres.Test.{Chat, Comment, Message, Post, PostLink, PostTag, PostView, Tag} require Ash.Query require Ash.Sort + import Ash.Expr test "multi-column sorts work" do Post @@ -289,4 +290,55 @@ defmodule AshPostgres.SortTest do assert DateTime.to_date(latest_post.created_at) == expected_date end + + describe "sorting by multiple has_one relationships" do + test "sorting by single calculated field through has_one relationship works" do + chat = Ash.create!(Chat, %{name: "Test Chat"}) + + # Create some messages + Ash.create!(Message, %{chat_id: chat.id, content: "First", sent_at: ~U[2025-01-01 10:00:00Z]}) + Ash.create!(Message, %{chat_id: chat.id, content: "Second", sent_at: ~U[2025-01-02 10:00:00Z], read_at: ~U[2025-01-02 11:00:00Z]}) + Ash.create!(Message, %{chat_id: chat.id, content: "Third", sent_at: ~U[2025-01-03 10:00:00Z]}) + + # This should work - sorting by single has_one relationship calculation + result = + Chat + |> Ash.Query.sort([{Ash.Sort.expr_sort(expr(last_unread_message.sent_at)), :desc}]) + |> Ash.read!() + + assert [%Chat{}] = result + end + + test "sorting by different single calculated field through has_one relationship works" do + chat = Ash.create!(Chat, %{name: "Test Chat"}) + + # Create some messages + Ash.create!(Message, %{chat_id: chat.id, content: "First", sent_at: ~U[2025-01-01 10:00:00Z]}) + Ash.create!(Message, %{chat_id: chat.id, content: "Second", sent_at: ~U[2025-01-02 10:00:00Z], read_at: ~U[2025-01-02 11:00:00Z]}) + + # This should work - sorting by different single has_one relationship calculation + result = + Chat + |> Ash.Query.sort([{Ash.Sort.expr_sort(expr(last_message.read_at)), :desc}]) + |> Ash.read!() + + assert [%Chat{}] = result + end + + test "sorting by multiple calculated fields through different has_one relationships fails" do + chat = Ash.create!(Chat, %{name: "Test Chat"}) + + # Create some messages + Ash.create!(Message, %{chat_id: chat.id, content: "First", sent_at: ~U[2025-01-01 10:00:00Z]}) + Ash.create!(Message, %{chat_id: chat.id, content: "Second", sent_at: ~U[2025-01-02 10:00:00Z], read_at: ~U[2025-01-02 11:00:00Z]}) + Ash.create!(Message, %{chat_id: chat.id, content: "Third", sent_at: ~U[2025-01-03 10:00:00Z]}) + + Chat + |> Ash.Query.sort([ + {Ash.Sort.expr_sort(expr(last_unread_message.sent_at)), :desc}, + {Ash.Sort.expr_sort(expr(last_message.read_at)), :desc} + ]) + |> Ash.read!() + end + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index 58802a18..2bf55a0b 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -48,6 +48,11 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.UnrelatedAggregatesTest.SecureProfile) resource(AshPostgres.Test.UnrelatedAggregatesTest.Report) resource(AshPostgres.Test.UnrelatedAggregatesTest.User) + resource(AshPostgres.Test.Customer) + resource(AshPostgres.Test.Product) + resource(AshPostgres.Test.Order) + resource(AshPostgres.Test.Chat) + resource(AshPostgres.Test.Message) end authorization do diff --git a/test/support/resources/chat.ex b/test/support/resources/chat.ex new file mode 100644 index 00000000..853c3c17 --- /dev/null +++ b/test/support/resources/chat.ex @@ -0,0 +1,40 @@ +defmodule AshPostgres.Test.Chat do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("chats") + repo(AshPostgres.TestRepo) + end + + actions do + default_accept(:*) + defaults([:create, :read, :destroy, :update]) + end + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string, public?: true) + end + + relationships do + has_many :messages, AshPostgres.Test.Message do + public?(true) + end + + has_one :last_message, AshPostgres.Test.Message do + public?(true) + from_many?(true) + sort(sent_at: :desc) + end + + has_one :last_unread_message, AshPostgres.Test.Message do + public?(true) + from_many?(true) + filter(expr(is_nil(read_at))) + sort(sent_at: :desc) + end + end +end \ No newline at end of file diff --git a/test/support/resources/customer.ex b/test/support/resources/customer.ex new file mode 100644 index 00000000..2bd94105 --- /dev/null +++ b/test/support/resources/customer.ex @@ -0,0 +1,35 @@ +defmodule AshPostgres.Test.Customer do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("customers") + repo(AshPostgres.TestRepo) + end + + actions do + default_accept(:*) + defaults([:create, :read, :destroy, :update]) + end + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string, public?: true) + end + + relationships do + has_many :orders, AshPostgres.Test.Order do + public?(true) + end + + # This relationship reproduces the bug described in: + # https://github.com/ash-project/ash_sql/issues/172#issuecomment-3264660128 + has_many :purchased_products, AshPostgres.Test.Product do + public?(true) + no_attributes?(true) + filter(expr(orders.customer_id == parent(id))) + end + end +end \ No newline at end of file diff --git a/test/support/resources/message.ex b/test/support/resources/message.ex new file mode 100644 index 00000000..6e0a2ded --- /dev/null +++ b/test/support/resources/message.ex @@ -0,0 +1,30 @@ +defmodule AshPostgres.Test.Message do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("messages") + repo(AshPostgres.TestRepo) + end + + actions do + default_accept(:*) + defaults([:create, :read, :destroy, :update]) + end + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:content, :string, public?: true) + attribute(:sent_at, :utc_datetime, default: &DateTime.utc_now/0, public?: true) + attribute(:read_at, :utc_datetime, public?: true) + end + + relationships do + belongs_to :chat, AshPostgres.Test.Chat do + public?(true) + allow_nil?(false) + end + end +end \ No newline at end of file diff --git a/test/support/resources/order.ex b/test/support/resources/order.ex new file mode 100644 index 00000000..83c6751a --- /dev/null +++ b/test/support/resources/order.ex @@ -0,0 +1,32 @@ +defmodule AshPostgres.Test.Order do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("orders") + repo(AshPostgres.TestRepo) + end + + actions do + default_accept(:*) + defaults([:create, :read, :destroy, :update]) + end + + attributes do + uuid_primary_key(:id, writable?: true) + end + + relationships do + belongs_to :customer, AshPostgres.Test.Customer do + public?(true) + allow_nil?(false) + end + + belongs_to :product, AshPostgres.Test.Product do + public?(true) + allow_nil?(false) + end + end +end \ No newline at end of file diff --git a/test/support/resources/product.ex b/test/support/resources/product.ex new file mode 100644 index 00000000..0ddb9169 --- /dev/null +++ b/test/support/resources/product.ex @@ -0,0 +1,33 @@ +defmodule AshPostgres.Test.Product do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table("products") + repo(AshPostgres.TestRepo) + end + + actions do + default_accept(:*) + defaults([:create, :read, :destroy, :update]) + end + + # This preparation reproduces the bug described in: + # https://github.com/ash-project/ash_sql/issues/172#issuecomment-3264660128 + preparations do + prepare build(sort: [:id]) + end + + attributes do + uuid_primary_key(:id, writable?: true) + attribute(:name, :string, public?: true) + end + + relationships do + has_many :orders, AshPostgres.Test.Order do + public?(true) + end + end +end \ No newline at end of file From 108dd845a49297b00dfd39c3260554ab565aafe5 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 8 Sep 2025 11:44:52 +0200 Subject: [PATCH 1156/1215] chore: format/credo --- test/distinct_test.exs | 6 ++-- test/parent_filter_test.exs | 2 +- test/sort_test.exs | 56 +++++++++++++++++++++++++----- test/support/resources/chat.ex | 2 +- test/support/resources/customer.ex | 2 +- test/support/resources/message.ex | 2 +- test/support/resources/order.ex | 2 +- test/support/resources/product.ex | 4 +-- 8 files changed, 58 insertions(+), 18 deletions(-) diff --git a/test/distinct_test.exs b/test/distinct_test.exs index 452f0068..284ed5b5 100644 --- a/test/distinct_test.exs +++ b/test/distinct_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.DistinctTest do @moduledoc false use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Customer, Post, Product, Order} + alias AshPostgres.Test.{Customer, Order, Post, Product} require Ash.Query @@ -179,7 +179,6 @@ defmodule AshPostgres.DistinctTest do end test "distinct breaks with prepare build(sort: [:id]) - original issue reproduction" do - customer = Customer |> Ash.Changeset.for_create(:create, %{name: "Test Customer"}) @@ -201,6 +200,7 @@ defmodule AshPostgres.DistinctTest do customer_with_products = Ash.load!(customer, :purchased_products) purchased_products = customer_with_products.purchased_products - assert length(purchased_products) == 1, "Expected 1 unique product, got #{length(purchased_products)} products. This indicates the distinct + sort preparation bug." + assert length(purchased_products) == 1, + "Expected 1 unique product, got #{length(purchased_products)} products. This indicates the distinct + sort preparation bug." end end diff --git a/test/parent_filter_test.exs b/test/parent_filter_test.exs index 78d295fe..aa2e7af3 100644 --- a/test/parent_filter_test.exs +++ b/test/parent_filter_test.exs @@ -1,7 +1,7 @@ defmodule AshPostgres.Test.ParentFilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Organization, Post, User, Comment} + alias AshPostgres.Test.{Comment, Organization, Post, User} require Ash.Query diff --git a/test/sort_test.exs b/test/sort_test.exs index 6be3113d..e7d949da 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -296,9 +296,24 @@ defmodule AshPostgres.SortTest do chat = Ash.create!(Chat, %{name: "Test Chat"}) # Create some messages - Ash.create!(Message, %{chat_id: chat.id, content: "First", sent_at: ~U[2025-01-01 10:00:00Z]}) - Ash.create!(Message, %{chat_id: chat.id, content: "Second", sent_at: ~U[2025-01-02 10:00:00Z], read_at: ~U[2025-01-02 11:00:00Z]}) - Ash.create!(Message, %{chat_id: chat.id, content: "Third", sent_at: ~U[2025-01-03 10:00:00Z]}) + Ash.create!(Message, %{ + chat_id: chat.id, + content: "First", + sent_at: ~U[2025-01-01 10:00:00Z] + }) + + Ash.create!(Message, %{ + chat_id: chat.id, + content: "Second", + sent_at: ~U[2025-01-02 10:00:00Z], + read_at: ~U[2025-01-02 11:00:00Z] + }) + + Ash.create!(Message, %{ + chat_id: chat.id, + content: "Third", + sent_at: ~U[2025-01-03 10:00:00Z] + }) # This should work - sorting by single has_one relationship calculation result = @@ -313,8 +328,18 @@ defmodule AshPostgres.SortTest do chat = Ash.create!(Chat, %{name: "Test Chat"}) # Create some messages - Ash.create!(Message, %{chat_id: chat.id, content: "First", sent_at: ~U[2025-01-01 10:00:00Z]}) - Ash.create!(Message, %{chat_id: chat.id, content: "Second", sent_at: ~U[2025-01-02 10:00:00Z], read_at: ~U[2025-01-02 11:00:00Z]}) + Ash.create!(Message, %{ + chat_id: chat.id, + content: "First", + sent_at: ~U[2025-01-01 10:00:00Z] + }) + + Ash.create!(Message, %{ + chat_id: chat.id, + content: "Second", + sent_at: ~U[2025-01-02 10:00:00Z], + read_at: ~U[2025-01-02 11:00:00Z] + }) # This should work - sorting by different single has_one relationship calculation result = @@ -329,9 +354,24 @@ defmodule AshPostgres.SortTest do chat = Ash.create!(Chat, %{name: "Test Chat"}) # Create some messages - Ash.create!(Message, %{chat_id: chat.id, content: "First", sent_at: ~U[2025-01-01 10:00:00Z]}) - Ash.create!(Message, %{chat_id: chat.id, content: "Second", sent_at: ~U[2025-01-02 10:00:00Z], read_at: ~U[2025-01-02 11:00:00Z]}) - Ash.create!(Message, %{chat_id: chat.id, content: "Third", sent_at: ~U[2025-01-03 10:00:00Z]}) + Ash.create!(Message, %{ + chat_id: chat.id, + content: "First", + sent_at: ~U[2025-01-01 10:00:00Z] + }) + + Ash.create!(Message, %{ + chat_id: chat.id, + content: "Second", + sent_at: ~U[2025-01-02 10:00:00Z], + read_at: ~U[2025-01-02 11:00:00Z] + }) + + Ash.create!(Message, %{ + chat_id: chat.id, + content: "Third", + sent_at: ~U[2025-01-03 10:00:00Z] + }) Chat |> Ash.Query.sort([ diff --git a/test/support/resources/chat.ex b/test/support/resources/chat.ex index 853c3c17..9a035f4c 100644 --- a/test/support/resources/chat.ex +++ b/test/support/resources/chat.ex @@ -37,4 +37,4 @@ defmodule AshPostgres.Test.Chat do sort(sent_at: :desc) end end -end \ No newline at end of file +end diff --git a/test/support/resources/customer.ex b/test/support/resources/customer.ex index 2bd94105..d5cff43e 100644 --- a/test/support/resources/customer.ex +++ b/test/support/resources/customer.ex @@ -32,4 +32,4 @@ defmodule AshPostgres.Test.Customer do filter(expr(orders.customer_id == parent(id))) end end -end \ No newline at end of file +end diff --git a/test/support/resources/message.ex b/test/support/resources/message.ex index 6e0a2ded..25896e04 100644 --- a/test/support/resources/message.ex +++ b/test/support/resources/message.ex @@ -27,4 +27,4 @@ defmodule AshPostgres.Test.Message do allow_nil?(false) end end -end \ No newline at end of file +end diff --git a/test/support/resources/order.ex b/test/support/resources/order.ex index 83c6751a..27566cb4 100644 --- a/test/support/resources/order.ex +++ b/test/support/resources/order.ex @@ -29,4 +29,4 @@ defmodule AshPostgres.Test.Order do allow_nil?(false) end end -end \ No newline at end of file +end diff --git a/test/support/resources/product.ex b/test/support/resources/product.ex index 0ddb9169..199bbff8 100644 --- a/test/support/resources/product.ex +++ b/test/support/resources/product.ex @@ -17,7 +17,7 @@ defmodule AshPostgres.Test.Product do # This preparation reproduces the bug described in: # https://github.com/ash-project/ash_sql/issues/172#issuecomment-3264660128 preparations do - prepare build(sort: [:id]) + prepare(build(sort: [:id])) end attributes do @@ -30,4 +30,4 @@ defmodule AshPostgres.Test.Product do public?(true) end end -end \ No newline at end of file +end From 2c4e6fdd19ce9217e08c85e00f67b44873bd58f4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 8 Sep 2025 16:08:06 +0200 Subject: [PATCH 1157/1215] chore: update test name --- test/parent_filter_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parent_filter_test.exs b/test/parent_filter_test.exs index aa2e7af3..b7f8f8dc 100644 --- a/test/parent_filter_test.exs +++ b/test/parent_filter_test.exs @@ -45,7 +45,7 @@ defmodule AshPostgres.Test.ParentFilterTest do |> Ash.read(authorize?: false) end - test "something else" do + test "aggregates from related filters are properly added to the query" do organization = Organization |> Ash.Changeset.for_create(:create, %{name: "test_org"}) From 14ec89d20b9af74adb27849106ce922d884f0b77 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 13 Sep 2025 17:32:02 +0200 Subject: [PATCH 1158/1215] test: tests and reproductions for get_path composition --- mix.lock | 8 +- .../test_repo/authors/20250908212414.json | 106 ++++++++++++++++++ .../20250908212414_migrate_resources61.exs | 21 ++++ test/storage_types_test.exs | 46 ++++++++ test/support/resources/author.ex | 5 +- 5 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/authors/20250908212414.json create mode 100644 priv/test_repo/migrations/20250908212414_migrate_resources61.exs diff --git a/mix.lock b/mix.lock index a6ba65e8..90a49a91 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,10 @@ %{ - "ash": {:hex, :ash, "3.5.35", "ff63b9d3670a05ad036ecc8732c0143770aff4e23f07662931a9ebd1f4aba3c4", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6ff48f80efabcc1867af9847c8656fa376d9b800e8988b82ec0a42bee4f6b58"}, - "ash_sql": {:hex, :ash_sql, "0.2.90", "6b533e963e4b7c0855654597d8eb7bb87c30db0be7328628cb9bb63e2e547a54", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "57a7235f22f01381b1a2d63364c353fe6fa31278f3bbd402aacab6dba78a7e6b"}, + "ash": {:hex, :ash, "3.5.40", "19b82796359f32520acd83581c5edfefd06e177fb3c15875f52d5f437f90d902", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "278959afb7e54fd3e7184650bfd4ee6a45979aa3d37913d9e19a7400097eae2d"}, + "ash_sql": {:hex, :ash_sql, "0.2.92", "7ef55c307944e68bfd9de07a186fda4bc3b6b7b401f5c5a5cdd0fafafe75d7d2", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "445fea8343a17d3842ad48007ef07b09ffa28209b3e03ea8772062c42fc50df1"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, - "db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"}, + "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, @@ -36,7 +36,7 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, + "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, "reactor": {:hex, :reactor, "0.15.6", "d717f9add549b25a089a94c90197718d2d838e35d81dd776b1d81587d4cf2aaa", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "74db98165e3644d86e0f723672d91ceca4339eaa935bcad7e78bf146a46d77b9"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, diff --git a/priv/resource_snapshots/test_repo/authors/20250908212414.json b/priv/resource_snapshots/test_repo/authors/20250908212414.json new file mode 100644 index 00000000..b271e003 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20250908212414.json @@ -0,0 +1,106 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "first_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "last_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "bio", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "bios", + "type": "jsonb" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "badges", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "settings", + "type": "jsonb" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "DD102A40219E8086BECBF5306AAA1196B59CC38B6FAA0BCEECFD24E82FF24DCD", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "authors" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20250908212414_migrate_resources61.exs b/priv/test_repo/migrations/20250908212414_migrate_resources61.exs new file mode 100644 index 00000000..303a4e53 --- /dev/null +++ b/priv/test_repo/migrations/20250908212414_migrate_resources61.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources61 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(:authors) do + add(:settings, :jsonb) + end + end + + def down do + alter table(:authors) do + remove(:settings) + end + end +end diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs index dde647b3..88783e54 100644 --- a/test/storage_types_test.exs +++ b/test/storage_types_test.exs @@ -42,4 +42,50 @@ defmodule AshPostgres.StorageTypesTest do assert author.bios == [%{"a" => 1}] end + + test "`in` operator works on get_path results" do + %{id: id} = + Author + |> Ash.Changeset.for_create( + :create, + %{ + first_name: "Test", + last_name: "User", + settings: %{ + "dues_reminders" => ["email", "sms"], + "newsletter" => ["email"], + "optional_field" => nil + } + } + ) + |> Ash.create!() + + assert [%Author{id: ^id}] = + Author + |> Ash.Query.filter("email" in settings["dues_reminders"]) + |> Ash.read!() + end + + test "`is_nil` operator works on get_path results" do + %{id: id} = + Author + |> Ash.Changeset.for_create( + :create, + %{ + first_name: "Test", + last_name: "User", + settings: %{ + "dues_reminders" => ["email", "sms"], + "newsletter" => ["email"], + "optional_field" => nil + } + } + ) + |> Ash.create!() + + assert [%Author{id: ^id}] = + Author + |> Ash.Query.filter(is_nil(settings["optional_field"])) + |> Ash.read!() + end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 54568518..77ad0d15 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -19,8 +19,8 @@ defmodule AshPostgres.Test.Author do table("authors") repo(AshPostgres.TestRepo) - migration_types bios: :jsonb - storage_types(bios: :jsonb) + migration_types bios: :jsonb, settings: :jsonb + storage_types(bios: :jsonb, settings: :jsonb) end attributes do @@ -30,6 +30,7 @@ defmodule AshPostgres.Test.Author do attribute(:bio, AshPostgres.Test.Bio, public?: true) attribute(:bios, {:array, :map}, public?: true) attribute(:badges, {:array, :atom}, public?: true) + attribute(:settings, :map, public?: true) end actions do From 19ed5ef68c6ab3d09e3e30169d838b17dbc30f21 Mon Sep 17 00:00:00 2001 From: Trond A Ekseth Date: Sat, 13 Sep 2025 17:57:54 +0200 Subject: [PATCH 1159/1215] docs: Fix duplicate `public` option in ash_postgres.gen.resources (#613) --- lib/mix/tasks/ash_postgres.gen.resources.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index a187f8d9..babac4f1 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -30,7 +30,6 @@ if Code.ensure_loaded?(Igniter) do - `public` - Mark all attributes and relationships as `public? true`. Defaults to `true`. - `no-migrations` - Do not generate snapshots & migrations for the resources. Defaults to `false`. - `skip-unknown` - Skip any attributes with types that we don't have a corresponding Elixir type for, and relationships that we can't assume the name of. - - `public` - Mark all attributes and relationships as `public? true`. Defaults to `true`. ## Tables From b2f98cf6de47daa094942be0f31f9f9fccfebb2c Mon Sep 17 00:00:00 2001 From: Trond A Ekseth Date: Mon, 15 Sep 2025 21:12:29 +0200 Subject: [PATCH 1160/1215] fix: Handle optional/empty input in relationship name guesser (#616) --- lib/resource_generator/spec.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 8b6d4ae6..f2da7ec5 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -781,7 +781,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do end Owl.IO.input(label: label, optional: true) - |> String.trim() + |> Kernel.||("") # common typo |> String.trim_leading(":") |> case do From f33cfbecd86870715de3532d69d23d66f7424a4d Mon Sep 17 00:00:00 2001 From: Moxley Stratton Date: Thu, 18 Sep 2025 04:12:32 -0700 Subject: [PATCH 1161/1215] test: Demonstrate failed query for is_nil on array within jsonb (#615) --- test/storage_types_test.exs | 3 ++- test/support/resources/author.ex | 2 +- test/support/resources/settings.ex | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 test/support/resources/settings.ex diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs index 88783e54..6b5a44cd 100644 --- a/test/storage_types_test.exs +++ b/test/storage_types_test.exs @@ -66,6 +66,7 @@ defmodule AshPostgres.StorageTypesTest do |> Ash.read!() end + @tag capture_log: false test "`is_nil` operator works on get_path results" do %{id: id} = Author @@ -85,7 +86,7 @@ defmodule AshPostgres.StorageTypesTest do assert [%Author{id: ^id}] = Author - |> Ash.Query.filter(is_nil(settings["optional_field"])) + |> Ash.Query.filter(is_nil(settings["dues_reminders"])) |> Ash.read!() end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 77ad0d15..e30f7592 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -30,7 +30,7 @@ defmodule AshPostgres.Test.Author do attribute(:bio, AshPostgres.Test.Bio, public?: true) attribute(:bios, {:array, :map}, public?: true) attribute(:badges, {:array, :atom}, public?: true) - attribute(:settings, :map, public?: true) + attribute(:settings, AshPostgres.Test.Settings, public?: true) end actions do diff --git a/test/support/resources/settings.ex b/test/support/resources/settings.ex new file mode 100644 index 00000000..681584db --- /dev/null +++ b/test/support/resources/settings.ex @@ -0,0 +1,10 @@ +defmodule AshPostgres.Test.Settings do + @moduledoc false + use Ash.Resource, data_layer: :embedded + + attributes do + attribute :dues_reminders, {:array, :string}, public?: true + attribute :newsletter, {:array, :string}, public?: true + attribute :optional_field, :string, public?: true + end +end From 850f7d267c8e2ffd6d558323327d01d74cff71b7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 18 Sep 2025 10:09:41 -0400 Subject: [PATCH 1162/1215] chore: update test to pass against ash_sql main --- test/storage_types_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs index 6b5a44cd..ca433860 100644 --- a/test/storage_types_test.exs +++ b/test/storage_types_test.exs @@ -86,7 +86,7 @@ defmodule AshPostgres.StorageTypesTest do assert [%Author{id: ^id}] = Author - |> Ash.Query.filter(is_nil(settings["dues_reminders"])) + |> Ash.Query.filter(not is_nil(settings["dues_reminders"])) |> Ash.read!() end end From 58c9543f2c8ce7ad8243a8296cf924c828c4e540 Mon Sep 17 00:00:00 2001 From: Robert Ellen Date: Fri, 19 Sep 2025 04:35:54 +1000 Subject: [PATCH 1163/1215] test: Add a test for an expr calc using an aggregation in a sort (#617) This replicates an issue seen. We have an expression calculation using a aggregation from a transitive relationship. If that calculation is used in a sort, the text of the expression is passed as the aggregate name, instead of a variable name such as "aggregate0" etc. E.g. ``` * ** (RuntimeError) Unbound aggregate field: "first(comments[field: :created_at, query: [filter: author_id == \"54c6b3da-d3a5-41f6-8409-780fed9fe184\"]])" ``` --- test/calculation_test.exs | 41 ++++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 28 +++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 68c975d9..cf539f18 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1076,4 +1076,45 @@ defmodule AshPostgres.CalculationTest do highest_score = hd(Enum.sort(scores, :desc)) assert post_with_highest_score.score == highest_score end + + test "an expression calculation can use an aggregate" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "my post"}) + |> Ash.create!() + + author = + Author + |> Ash.Changeset.for_create(:create, %{ + first_name: "ashley" + }) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "first comment"}) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + first_comment_by_author = + Comment + |> Ash.Changeset.for_create(:create, %{title: "first comment by Ashley"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) + |> Ash.create!() + + post = + Post + |> Ash.Query.for_read(:read_by_comment_author, %{ + author_id: author.id + }) + |> Ash.Query.sort_input([ + {"datetime_of_first_comment_by_author", {%{author_id: author.id}, :desc}} + ]) + |> Ash.read_one!() + + assert DateTime.compare( + post.datetime_of_first_comment_by_author, + first_comment_by_author.created_at + ) + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index e6009d57..817e0ff9 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -488,6 +488,20 @@ defmodule AshPostgres.Test.Post do pagination(keyset?: true, default_limit: 25) filter(expr(count_nils(latest_comment.linked_comment_post_ids) == 0)) end + + read :read_by_comment_author do + argument(:author_id, :uuid, allow_nil?: false) + + filter(expr(comments.author_id == ^arg(:author_id))) + + prepare( + build( + load: [ + datetime_of_first_comment_by_author: %{author_id: arg(:author_id)} + ] + ) + ) + end end identities do @@ -1030,6 +1044,20 @@ defmodule AshPostgres.Test.Post do calculate(:latest_comment_title, :string, expr(latest_comment.title), allow_nil?: true) calculate(:concated_comment_titles, :string, expr(fragment("concat(?)", comment_titles))) + + calculate :datetime_of_first_comment_by_author, + :utc_datetime_usec, + expr( + first(comments, + field: :created_at, + query: [ + filter: author_id == ^arg(:author_id) + ] + ) + ) do + public?(true) + argument(:author_id, :uuid, allow_nil?: false) + end end aggregates do From 1e09ef3e8031fea0f7fb62880468aa6c9b44279b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Sep 2025 07:37:03 -0400 Subject: [PATCH 1164/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 90a49a91..602a8e67 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.40", "19b82796359f32520acd83581c5edfefd06e177fb3c15875f52d5f437f90d902", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "278959afb7e54fd3e7184650bfd4ee6a45979aa3d37913d9e19a7400097eae2d"}, - "ash_sql": {:hex, :ash_sql, "0.2.92", "7ef55c307944e68bfd9de07a186fda4bc3b6b7b401f5c5a5cdd0fafafe75d7d2", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "445fea8343a17d3842ad48007ef07b09ffa28209b3e03ea8772062c42fc50df1"}, + "ash_sql": {:hex, :ash_sql, "0.2.93", "d2e50a718f18e67bffa8fd9c7bea39d260ca746ca4df357bd9726a3ad4a39294", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "492d811a636c19dad990c4f1af83761c0006ec5650970252f78cf4bd2b50b500"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From 798f37d0250e7298fd3b1fb60f197b19ddca2596 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 19 Sep 2025 07:37:24 -0400 Subject: [PATCH 1165/1215] chore: release version v2.6.18 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08169773..09e31680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.18](https://github.com/ash-project/ash_postgres/compare/v2.6.17...v2.6.18) (2025-09-19) + + + + +### Bug Fixes: + +* Handle optional/empty input in relationship name guesser (#616) by Trond A Ekseth + +* properly handle sorts w/ parent refs on lateral joins by Zach Daniel + +* annotate unrelated exists expressions as supported by Zach Daniel + ## [v2.6.17](https://github.com/ash-project/ash_postgres/compare/v2.6.16...v2.6.17) (2025-08-31) diff --git a/mix.exs b/mix.exs index 3e8719c1..ebb4daba 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.17" + @version "2.6.18" def project do [ From cd34939b5b4f9359d1d8b1c6056359fbfeb05795 Mon Sep 17 00:00:00 2001 From: Kantum <43590963+kantum@users.noreply.github.com> Date: Sat, 20 Sep 2025 21:05:06 +0200 Subject: [PATCH 1166/1215] docs: list_tenants -> all_tenants (#619) --- documentation/topics/development/migrations-and-tasks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 04fefc93..5fd26271 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -19,9 +19,9 @@ dev migrations and run them. For more information on generating migrations, run `mix help ash_postgres.generate_migrations` (the underlying task that is called by `mix ash.migrate`) -> ### list_tenants/0 {: .info} +> ### all_tenants/0 {: .info} > -> If you have are using schema-based multitenancy, you will also need to define a `list_tenants/0` function in your repo module. See `AshPostgres.Repo` for more. +> If you have are using schema-based multitenancy, you will also need to define a `all_tenants/0` function in your repo module. See `AshPostgres.Repo` for more. ### Regenerating Migrations From 98ea317e02dfe6034603f1be60ba2a895cadd2d0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 20 Sep 2025 17:43:17 -0400 Subject: [PATCH 1167/1215] fix: fix conditional on installing ash in installer --- lib/mix/tasks/ash_postgres.install.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 5bfa39f4..6808cbfb 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -39,6 +39,8 @@ if Code.ensure_loaded?(Igniter) do igniter = if Igniter.Project.Deps.has_dep?(igniter, :ash) do igniter + else + igniter |> Igniter.Project.Deps.add_dep({:ash, "~> 3.0"}, yes: opts[:yes]) |> then(fn %{assigns: %{test_mode?: true}} = igniter -> @@ -52,8 +54,6 @@ if Code.ensure_loaded?(Igniter) do ) end) |> Igniter.compose_task("ash.install", argv) - else - igniter end igniter From dce5c4b3e29546fff543df12b26d8adf8fa586f9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 20 Sep 2025 17:43:42 -0400 Subject: [PATCH 1168/1215] chore: release version v2.6.19 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09e31680..4b9eaa03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.19](https://github.com/ash-project/ash_postgres/compare/v2.6.18...v2.6.19) (2025-09-20) + + + + +### Bug Fixes: + +* fix conditional on installing ash in installer by Zach Daniel + ## [v2.6.18](https://github.com/ash-project/ash_postgres/compare/v2.6.17...v2.6.18) (2025-09-19) diff --git a/mix.exs b/mix.exs index ebb4daba..22d502f0 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.18" + @version "2.6.19" def project do [ From 53d2a9525eb726a45f81bbaba59f3e93efcd4065 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 20 Sep 2025 23:03:39 -0400 Subject: [PATCH 1169/1215] fix: use `:mutate` repo for on_transaction_begin callback --- lib/data_layer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index e4cde133..43b8f5a1 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3561,7 +3561,7 @@ defmodule AshPostgres.DataLayer do repo _ -> - AshPostgres.DataLayer.Info.repo(resource, :read) + AshPostgres.DataLayer.Info.repo(resource, :mutate) end func = fn -> From ee2d2f5f9dea646c4a3bccd7a2f6f19b22f878b9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 21 Sep 2025 09:47:46 -0400 Subject: [PATCH 1170/1215] chore: remove ash installation step in ash_postgres --- lib/mix/tasks/ash_postgres.install.ex | 21 --------------------- mix.lock | 8 ++++---- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 6808cbfb..4935fdf1 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -22,7 +22,6 @@ if Code.ensure_loaded?(Igniter) do @impl true def igniter(igniter) do - argv = igniter.args.argv opts = igniter.args.options repo = @@ -36,26 +35,6 @@ if Code.ensure_loaded?(Igniter) do otp_app = Igniter.Project.Application.app_name(igniter) - igniter = - if Igniter.Project.Deps.has_dep?(igniter, :ash) do - igniter - else - igniter - |> Igniter.Project.Deps.add_dep({:ash, "~> 3.0"}, yes: opts[:yes]) - |> then(fn - %{assigns: %{test_mode?: true}} = igniter -> - igniter - - igniter -> - Igniter.apply_and_fetch_dependencies(igniter, - error_on_abort?: true, - yes: opts[:yes], - yes_to_deps: true - ) - end) - |> Igniter.compose_task("ash.install", argv) - end - igniter |> Igniter.Project.Formatter.import_dep(:ash_postgres) |> setup_aliases() diff --git a/mix.lock b/mix.lock index 602a8e67..78a54f83 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.40", "19b82796359f32520acd83581c5edfefd06e177fb3c15875f52d5f437f90d902", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "278959afb7e54fd3e7184650bfd4ee6a45979aa3d37913d9e19a7400097eae2d"}, + "ash": {:hex, :ash, "3.5.42", "bdd84c468c05e497a8b1ee579901274125c540b66df82ed67c35f889a529ee70", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc0988e171630401e8eab65c95ac33c04b8fe47084fc038e80f4d713cb2d44ba"}, "ash_sql": {:hex, :ash_sql, "0.2.93", "d2e50a718f18e67bffa8fd9c7bea39d260ca746ca4df357bd9726a3ad4a39294", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "492d811a636c19dad990c4f1af83761c0006ec5650970252f78cf4bd2b50b500"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, @@ -9,7 +9,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.2", "7d0c0863f3fc8d71d17fc3ad3b9424beae13f02712ad84191a826c7169484f01", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "669d9291370513ff56e7b7e7081b7af3283d02e046cf3d403053c557894a0b3e"}, + "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.28", "9db10192f19f10b924f14c805f5b2ad992617fccaff9cf9582b7f065d562d4d8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ad9369d626aeca21079ef17661a2672fb32598610c5e5bccae2537efd36b27d4"}, + "igniter": {:hex, :igniter, "0.6.29", "58ec46e601445df5ff3d98e65dce1305b10f23391d8f43558bc22bcbb3e1a7f0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b45a5c70dcee77afd81a71c707fda7d0a8637061c60cdee639dc38d049443e02"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -38,7 +38,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, - "reactor": {:hex, :reactor, "0.15.6", "d717f9add549b25a089a94c90197718d2d838e35d81dd776b1d81587d4cf2aaa", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "74db98165e3644d86e0f723672d91ceca4339eaa935bcad7e78bf146a46d77b9"}, + "reactor": {:hex, :reactor, "0.16.0", "394087fe0f01b09e5cbcbf6525d9a54cd484582214e0e9e59f69ebc8d79eb70c", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "9ac43e70a9a36c5a016b02b6c068933dfd36edc0e3abd9cd6325a30194900c66"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, From fefb612c3c873b65638e68df45f12edc41dcbd81 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 21 Sep 2025 22:02:04 -0400 Subject: [PATCH 1171/1215] chore: format --- test/support/resources/settings.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/support/resources/settings.ex b/test/support/resources/settings.ex index 681584db..b24b3afc 100644 --- a/test/support/resources/settings.ex +++ b/test/support/resources/settings.ex @@ -3,8 +3,8 @@ defmodule AshPostgres.Test.Settings do use Ash.Resource, data_layer: :embedded attributes do - attribute :dues_reminders, {:array, :string}, public?: true - attribute :newsletter, {:array, :string}, public?: true - attribute :optional_field, :string, public?: true + attribute(:dues_reminders, {:array, :string}, public?: true) + attribute(:newsletter, {:array, :string}, public?: true) + attribute(:optional_field, :string, public?: true) end end From 36ccc75f52fcc545a6bccf45d0531e277bc38fde Mon Sep 17 00:00:00 2001 From: siassaj Date: Sat, 27 Sep 2025 11:59:43 +0300 Subject: [PATCH 1172/1215] improvement: use default constraint of 'now()' for AshPostgres.Timestamptz (#621) AshPostgres.Timestamptz creates a pg column of type 'timestamptz', however the existing default behaviour was to implement the default constraint as a 'timestamp' type. Effectively 'now()::timestamptz' was converted to 'timestamp' then back to 'timestamptz'. This has been changed to 'now()'. --- lib/migration_generator/migration_generator.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 86920ed4..1a3a396d 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3489,7 +3489,7 @@ defmodule AshPostgres.MigrationGenerator do @uuid_functions [&Ash.UUID.generate/0, &Ecto.UUID.generate/0] - defp default(%{name: name, default: default}, resource, _repo) when is_function(default) do + defp default(%{name: name, default: default, type: type}, resource, _repo) when is_function(default) do configured_default(resource, name) || cond do default in @uuid_functions -> @@ -3498,6 +3498,9 @@ defmodule AshPostgres.MigrationGenerator do default == (&Ash.UUIDv7.generate/0) -> ~S[fragment("uuid_generate_v7()")] + default == (&DateTime.utc_now/0) && type == AshPostgres.Timestamptz -> + ~S[fragment("now()")] + default == (&DateTime.utc_now/0) -> ~S[fragment("(now() AT TIME ZONE 'utc')")] From 4021b73e2609439c0cf853c7837b118bebfa1fc0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Sep 2025 05:26:12 -0400 Subject: [PATCH 1173/1215] chore: update spark & fix warnings --- lib/check_constraint.ex | 2 +- lib/custom_index.ex | 3 ++- lib/reference.ex | 1 + lib/statement.ex | 3 ++- mix.exs | 1 + mix.lock | 4 ++-- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/check_constraint.ex b/lib/check_constraint.ex index 3cfeecd7..a16e7346 100644 --- a/lib/check_constraint.ex +++ b/lib/check_constraint.ex @@ -1,7 +1,7 @@ defmodule AshPostgres.CheckConstraint do @moduledoc "Represents a configured check constraint on the table backing a resource" - defstruct [:attribute, :name, :message, :check] + defstruct [:attribute, :name, :message, :check, :__spark_metadata__] def schema do [ diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 9d037331..9972e4da 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -13,7 +13,8 @@ defmodule AshPostgres.CustomIndex do :include, :nulls_distinct, :message, - :all_tenants? + :all_tenants?, + :__spark_metadata__ ] defstruct @fields diff --git a/lib/reference.ex b/lib/reference.ex index 724251e0..e724a0b6 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -9,6 +9,7 @@ defmodule AshPostgres.Reference do :match_type, :deferrable, :index?, + :__spark_metadata__, ignore?: false ] diff --git a/lib/statement.ex b/lib/statement.ex index ae04496e..ba51622d 100644 --- a/lib/statement.ex +++ b/lib/statement.ex @@ -5,7 +5,8 @@ defmodule AshPostgres.Statement do :name, :up, :down, - :code? + :code?, + :__spark_metadata__ ] defstruct @fields diff --git a/mix.exs b/mix.exs index 22d502f0..6c02fab0 100644 --- a/mix.exs +++ b/mix.exs @@ -167,6 +167,7 @@ defmodule AshPostgres.MixProject do defp deps do [ {:ash, ash_version("~> 3.5 and >= 3.5.35")}, + {:spark, "~> 2.3 and >= 2.3.4"}, {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.90")}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index 78a54f83..90c38313 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.29", "58ec46e601445df5ff3d98e65dce1305b10f23391d8f43558bc22bcbb3e1a7f0", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b45a5c70dcee77afd81a71c707fda7d0a8637061c60cdee639dc38d049443e02"}, + "igniter": {:hex, :igniter, "0.6.30", "83a466369ebb8fe009e0823c7bf04314dc545122c2d48f896172fc79df33e99d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "76a14d5b7f850bb03b5243088c3649d54a2e52e34a2aa1104dee23cf50a8bae0"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, @@ -44,7 +44,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.2.68", "4c4547c88d73311e3157bc402ab27f3a7bbd5e55a2ee92bcf7cd3a0a475d201e", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "2fa4dd89801415a098a421589633f0e3df7ed9ff4046e80a65d35a413bc0d194"}, + "spark": {:hex, :spark, "2.3.4", "3fe37fdfa01e3f7c9f4ced16b7b9950d5bfbb7fab024148e1968b0460ce1336b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c0293d41461f1c1f1774379c668a240b008f4f8dcd550ea82b06163a55dcd53b"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, From 702ce5e56052a8ffd5399a9b52f7f9b2df94bf84 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Sep 2025 05:58:47 -0400 Subject: [PATCH 1174/1215] chore: update tests to account for warnings --- test/aggregate_test.exs | 55 ++++++++++++++++++--------------- test/references_test.exs | 66 +++++++++++++++++++++------------------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index d17b753b..e26ca964 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1,5 +1,6 @@ defmodule AshSql.AggregateTest do use AshPostgres.RepoCase, async: false + import ExUnit.CaptureIO alias AshPostgres.Test.{Author, Comment, Organization, Post, Rating, User} require Ash.Query @@ -970,37 +971,41 @@ defmodule AshSql.AggregateTest do end test "can't define multidimensional array aggregate types" do - assert_raise Spark.Error.DslError, ~r/Aggregate not supported/, fn -> - defmodule Foo do - @moduledoc false - use Ash.Resource, - domain: nil, - data_layer: AshPostgres.DataLayer - - postgres do - table("profile") - repo(AshPostgres.TestRepo) - end + # This used to raise an error, but now should only emit a warning and allow the module to compile + {_, io} = + with_io(:stderr, fn -> + defmodule Foo do + @moduledoc false + use Ash.Resource, + domain: nil, + data_layer: AshPostgres.DataLayer + + postgres do + table("profile") + repo(AshPostgres.TestRepo) + end - attributes do - uuid_primary_key(:id, writable?: true) - end + attributes do + uuid_primary_key(:id, writable?: true) + end - actions do - defaults([:create, :read, :update, :destroy]) - end + actions do + defaults([:create, :read, :update, :destroy]) + end - relationships do - belongs_to(:author, AshPostgres.Test.Author) do - public?(true) + relationships do + belongs_to(:author, AshPostgres.Test.Author) do + public?(true) + end end - end - aggregates do - first(:author_badges, :author, :badges) + aggregates do + first(:author_badges, :author, :badges) + end end - end - end + end) + + assert io =~ "Aggregate not supported" end test "related aggregates can be filtered on" do diff --git a/test/references_test.exs b/test/references_test.exs index a8be1d07..7632c9f8 100644 --- a/test/references_test.exs +++ b/test/references_test.exs @@ -1,5 +1,6 @@ defmodule AshPostgres.ReferencesTest do use AshPostgres.RepoCase + import ExUnit.CaptureIO test "can't use match_type != :full when referencing an non-primary key index" do Code.compiler_options(ignore_module_conflict: true) @@ -66,44 +67,47 @@ defmodule AshPostgres.ReferencesTest do end end - assert_raise Spark.Error.DslError, ~r/Unsupported match_type./, fn -> - defmodule UserThing do - @moduledoc false - use Ash.Resource, - domain: nil, - data_layer: AshPostgres.DataLayer - - attributes do - attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) - attribute(:name, :string, public?: true) - attribute(:org_id, :uuid, public?: true) - attribute(:foo_id, :uuid, public?: true) - end + {_, io} = + with_io(:stderr, fn -> + defmodule UserThing do + @moduledoc false + use Ash.Resource, + domain: nil, + data_layer: AshPostgres.DataLayer + + attributes do + attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true) + attribute(:name, :string, public?: true) + attribute(:org_id, :uuid, public?: true) + attribute(:foo_id, :uuid, public?: true) + end - multitenancy do - strategy(:attribute) - attribute(:org_id) - end + multitenancy do + strategy(:attribute) + attribute(:org_id) + end - relationships do - belongs_to(:org, Org) - belongs_to(:user, User, destination_attribute: :secondary_id) - end + relationships do + belongs_to(:org, Org) + belongs_to(:user, User, destination_attribute: :secondary_id) + end - postgres do - table("user_things") - repo(AshPostgres.TestRepo) + postgres do + table("user_things") + repo(AshPostgres.TestRepo) - references do - reference :user, match_with: [foo_id: :foo_id], match_type: :simple + references do + reference :user, match_with: [foo_id: :foo_id], match_type: :simple + end end - end - actions do - defaults([:create, :read, :update, :destroy]) + actions do + defaults([:create, :read, :update, :destroy]) + end end - end - end + end) + + assert io =~ "Unsupported match_type" end test "named reference results in properly applied foreign_key_constraint/3 on the underlying changeset" do From a35894b08caf915e3b205cd19f6b3735b30cc2aa Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Sep 2025 06:18:14 -0400 Subject: [PATCH 1175/1215] improvement: location in spark errors and migration generator fixes --- lib/custom_index.ex | 5 ++--- lib/migration_generator/migration_generator.ex | 8 ++++++-- lib/statement.ex | 5 ++--- lib/verifiers/ensure_table_or_polymorphic.ex | 3 ++- ...ent_attribute_multitenancy_and_non_full_match_type.ex | 3 ++- .../prevent_multidimensional_array_aggregates.ex | 3 ++- lib/verifiers/validate_identity_index_names.ex | 9 ++++++--- lib/verifiers/validate_references.ex | 3 ++- 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 9972e4da..29496ceb 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -13,11 +13,10 @@ defmodule AshPostgres.CustomIndex do :include, :nulls_distinct, :message, - :all_tenants?, - :__spark_metadata__ + :all_tenants? ] - defstruct @fields + defstruct @fields ++ [:__spark_metadata__] def fields, do: @fields diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 1a3a396d..c0a1abac 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3044,6 +3044,7 @@ defmodule AshPostgres.MigrationGenerator do identity_index_names[identity.name] || "#{relationship.context[:data_layer][:table]}_#{identity.name}_index" ) + |> Map.delete(:__spark_metadata__) end) end) |> Map.update!(:attributes, fn attributes -> @@ -3057,7 +3058,9 @@ defmodule AshPostgres.MigrationGenerator do source_attribute = Ash.Resource.Info.attribute(relationship.source, relationship.source_attribute) - Map.put(attribute, :references, %{ + attribute + |> Map.delete(:__spark_metadata__) + |> Map.put(:references, %{ destination_attribute: source_attribute.source, destination_attribute_default: default( @@ -3489,7 +3492,8 @@ defmodule AshPostgres.MigrationGenerator do @uuid_functions [&Ash.UUID.generate/0, &Ecto.UUID.generate/0] - defp default(%{name: name, default: default, type: type}, resource, _repo) when is_function(default) do + defp default(%{name: name, default: default, type: type}, resource, _repo) + when is_function(default) do configured_default(resource, name) || cond do default in @uuid_functions -> diff --git a/lib/statement.ex b/lib/statement.ex index ba51622d..dfc49322 100644 --- a/lib/statement.ex +++ b/lib/statement.ex @@ -5,11 +5,10 @@ defmodule AshPostgres.Statement do :name, :up, :down, - :code?, - :__spark_metadata__ + :code? ] - defstruct @fields + defstruct @fields ++ [:__spark_metadata__] def fields, do: @fields diff --git a/lib/verifiers/ensure_table_or_polymorphic.ex b/lib/verifiers/ensure_table_or_polymorphic.ex index 1f60b8ed..c2e612ca 100644 --- a/lib/verifiers/ensure_table_or_polymorphic.ex +++ b/lib/verifiers/ensure_table_or_polymorphic.ex @@ -24,7 +24,8 @@ defmodule AshPostgres.Verifiers.EnsureTableOrPolymorphic do end ``` """, - path: [:postgres, :table] + path: [:postgres, :table], + location: Spark.Dsl.Transformer.get_section_anno(dsl, [:postgres]) end end end diff --git a/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex index 0d9e71c8..5e0a25cc 100644 --- a/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex +++ b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex @@ -23,7 +23,8 @@ defmodule AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType The reference #{inspect(resource)}.#{reference.relationship} can't have `match_type: :#{reference.match_type}` because it's referencing another multitenant resource with attribute strategy using a non-primary key index, which requires using `match_type: :full`. """, - path: [:postgres, :references, reference.relationship] + path: [:postgres, :references, reference.relationship], + location: Spark.Dsl.Transformer.get_section_anno(dsl, [:postgres, :references]) else :ok end diff --git a/lib/verifiers/prevent_multidimensional_array_aggregates.ex b/lib/verifiers/prevent_multidimensional_array_aggregates.ex index cdb688b4..5bea26eb 100644 --- a/lib/verifiers/prevent_multidimensional_array_aggregates.ex +++ b/lib/verifiers/prevent_multidimensional_array_aggregates.ex @@ -35,7 +35,8 @@ defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do Postgres does not support multidimensional arrays with differing lengths internally. In the future we may be able to remove this restriction for the `:first` type aggregate, but likely never for `:list`. In the meantime, you will have to use a custom calculation to get this data. - """ + """, + location: Ash.Resource.Info.aggregate(resource, aggregate.name) |> Spark.Dsl.Entity.anno() _ -> :ok diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index 269e881e..91ef203c 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -14,7 +14,8 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do module: Verifier.get_persisted(dsl, :module), message: """ Identity #{identity} has a name that is too long. Names must be 63 characters or less. - """ + """, + location: Spark.Dsl.Transformer.get_opt_anno(dsl, [:postgres, :identity_index_names], identity) end end) @@ -36,7 +37,8 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do Multiple identities would result in the same index name: #{name} Identities: #{inspect(Enum.map(identities, & &1.name))} - """ + """, + location: Spark.Dsl.Transformer.get_section_anno(dsl, [:postgres, :identity_index_names]) {name, [identity]} -> if String.length(name) > 63 do @@ -51,7 +53,8 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do postgres do identity_index_names #{identity.name}: "a_shorter_name" end - """ + """, + location: Spark.Dsl.Entity.anno(identity) end end) end diff --git a/lib/verifiers/validate_references.ex b/lib/verifiers/validate_references.ex index 3f2c90aa..40c47a8e 100644 --- a/lib/verifiers/validate_references.ex +++ b/lib/verifiers/validate_references.ex @@ -12,7 +12,8 @@ defmodule AshPostgres.Verifiers.ValidateReferences do path: [:postgres, :references, reference.relationship], module: Verifier.get_persisted(dsl, :module), message: - "Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists" + "Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists", + location: Spark.Dsl.Transformer.get_section_anno(dsl, [:postgres, :references]) end end) From 03d26cbbb4fdda321834267a10ef72e119180763 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Sep 2025 06:29:51 -0400 Subject: [PATCH 1176/1215] chore: more fixes around spark_metadata --- lib/migration_generator/migration_generator.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index c0a1abac..fcb44c95 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3580,6 +3580,7 @@ defmodule AshPostgres.MigrationGenerator do end) %{index | fields: fields} + |> Map.delete(:__spark_metadata__) end) end) |> Map.update!(:identities, fn identities -> @@ -3591,6 +3592,7 @@ defmodule AshPostgres.MigrationGenerator do end) %{identity | keys: keys} + |> Map.delete(:__spark_metadata__) end) end) |> to_ordered_object() @@ -3610,6 +3612,7 @@ defmodule AshPostgres.MigrationGenerator do |> Map.update!(:type, fn type -> sanitize_type(type, attribute[:size], attribute[:precision], attribute[:scale]) end) + |> Map.delete(:__spark_metadata__) end defp references_on_delete_to_binary(value) when is_atom(value), do: value From 7be8af41ffdd4650c2e70d380b2b44b20d2faf6e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Sep 2025 06:31:44 -0400 Subject: [PATCH 1177/1215] chore: format --- lib/verifiers/prevent_multidimensional_array_aggregates.ex | 3 ++- lib/verifiers/validate_identity_index_names.ex | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/verifiers/prevent_multidimensional_array_aggregates.ex b/lib/verifiers/prevent_multidimensional_array_aggregates.ex index 5bea26eb..82a38202 100644 --- a/lib/verifiers/prevent_multidimensional_array_aggregates.ex +++ b/lib/verifiers/prevent_multidimensional_array_aggregates.ex @@ -36,7 +36,8 @@ defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do Postgres does not support multidimensional arrays with differing lengths internally. In the future we may be able to remove this restriction for the `:first` type aggregate, but likely never for `:list`. In the meantime, you will have to use a custom calculation to get this data. """, - location: Ash.Resource.Info.aggregate(resource, aggregate.name) |> Spark.Dsl.Entity.anno() + location: + Ash.Resource.Info.aggregate(resource, aggregate.name) |> Spark.Dsl.Entity.anno() _ -> :ok diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index 91ef203c..82457eb2 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -15,7 +15,8 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do message: """ Identity #{identity} has a name that is too long. Names must be 63 characters or less. """, - location: Spark.Dsl.Transformer.get_opt_anno(dsl, [:postgres, :identity_index_names], identity) + location: + Spark.Dsl.Transformer.get_opt_anno(dsl, [:postgres, :identity_index_names], identity) end end) @@ -38,7 +39,8 @@ defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do Identities: #{inspect(Enum.map(identities, & &1.name))} """, - location: Spark.Dsl.Transformer.get_section_anno(dsl, [:postgres, :identity_index_names]) + location: + Spark.Dsl.Transformer.get_section_anno(dsl, [:postgres, :identity_index_names]) {name, [identity]} -> if String.length(name) > 63 do From ed3ff07c8c1287b36f2ad4925c79c5590bc38e6c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 27 Sep 2025 06:31:55 -0400 Subject: [PATCH 1178/1215] chore: release version v2.6.20 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b9eaa03..f6546f3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.20](https://github.com/ash-project/ash_postgres/compare/v2.6.19...v2.6.20) (2025-09-27) + + + + +### Bug Fixes: + +* use `:mutate` repo for on_transaction_begin callback by Zach Daniel + +### Improvements: + +* location in spark errors and migration generator fixes by Zach Daniel + +* use default constraint of 'now()' for AshPostgres.Timestamptz (#621) by siassaj + ## [v2.6.19](https://github.com/ash-project/ash_postgres/compare/v2.6.18...v2.6.19) (2025-09-20) diff --git a/mix.exs b/mix.exs index 6c02fab0..00778fec 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.19" + @version "2.6.20" def project do [ From 1130a0235da46e82d452ea9ca08b415cebf8678a Mon Sep 17 00:00:00 2001 From: Chris O'Donnell Date: Sun, 28 Sep 2025 21:11:05 -0400 Subject: [PATCH 1179/1215] test: write failing test to illustrate regression (#622) --- test/aggregate_test.exs | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index e26ca964..4803ec5c 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1683,4 +1683,51 @@ defmodule AshSql.AggregateTest do |> Ash.read_one!() end end + + @tag :regression + test "count is accurate" do + org = + AshPostgres.Test.Organization + |> Ash.Changeset.for_create(:create, %{name: "Test Org"}) + |> Ash.create!() + + user = + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "test_user", organization_id: org.id}) + |> Ash.create!() + + AshPostgres.Test.User + |> Ash.Changeset.for_create(:create, %{name: "another_user", organization_id: org.id}) + |> Ash.create!() + + author = + AshPostgres.Test.Author + |> Ash.Changeset.for_create(:create, %{first_name: "Test", last_name: "Author"}) + |> Ash.create!() + + post = + AshPostgres.Test.Post + |> Ash.Changeset.for_create(:create, %{ + title: "Test Post", + organization_id: org.id, + author_id: author.id + }) + |> Ash.create!() + + AshPostgres.Test.Comment + |> Ash.Changeset.for_create(:create, %{ + title: "First comment", + post_id: post.id, + author_id: author.id + }) + |> Ash.create!() + + loaded_post = + AshPostgres.Test.Post + |> Ash.Query.filter(id == ^post.id) + |> Ash.Query.load(:count_of_comments) + |> Ash.read_one!(actor: user) + + assert loaded_post.count_of_comments == 1 + end end From 28862b1c78ebb1277f7571824e39b7f8ec689a12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:19:13 -0400 Subject: [PATCH 1180/1215] chore(deps): bump the production-dependencies group with 3 updates (#625) Bumps the production-dependencies group with 3 updates: [ash](https://github.com/ash-project/ash), [ash_sql](https://github.com/ash-project/ash_sql) and [spark](https://github.com/ash-project/spark). Updates `ash` from 3.5.42 to 3.5.43 - [Release notes](https://github.com/ash-project/ash/releases) - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.42...v3.5.43) Updates `ash_sql` from 0.2.93 to 0.3.0 - [Release notes](https://github.com/ash-project/ash_sql/releases) - [Changelog](https://github.com/ash-project/ash_sql/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash_sql/compare/v0.2.93...v0.3.0) Updates `spark` from 2.3.4 to 2.3.5 - [Release notes](https://github.com/ash-project/spark/releases) - [Changelog](https://github.com/ash-project/spark/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/spark/compare/v2.3.4...v2.3.5) --- updated-dependencies: - dependency-name: ash dependency-version: 3.5.43 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ash_sql dependency-version: 0.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: spark dependency-version: 2.3.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index 90c38313..726124db 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "ash": {:hex, :ash, "3.5.42", "bdd84c468c05e497a8b1ee579901274125c540b66df82ed67c35f889a529ee70", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.68 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc0988e171630401e8eab65c95ac33c04b8fe47084fc038e80f4d713cb2d44ba"}, - "ash_sql": {:hex, :ash_sql, "0.2.93", "d2e50a718f18e67bffa8fd9c7bea39d260ca746ca4df357bd9726a3ad4a39294", [:mix], [{:ash, ">= 3.5.35 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "492d811a636c19dad990c4f1af83761c0006ec5650970252f78cf4bd2b50b500"}, + "ash": {:hex, :ash, "3.5.43", "222f9a8ac26ad3b029f8e69306cc83091c992d858b4538af12e33a148f301cab", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "48b2aa274c524f5b968c563dd56aec8f9b278c529c8aa46e6fe0ca564c26cc1c"}, + "ash_sql": {:hex, :ash_sql, "0.3.0", "2c43ddcc8c7fb51dc25ba3bca965d8b68e7aaecb290cabfed3cf213965aca937", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ef03759d6a1d4cb189fcadbd183cf047f0565d7d88ea8612d85c9e6e724835e7"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -38,13 +38,13 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, - "reactor": {:hex, :reactor, "0.16.0", "394087fe0f01b09e5cbcbf6525d9a54cd484582214e0e9e59f69ebc8d79eb70c", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "9ac43e70a9a36c5a016b02b6c068933dfd36edc0e3abd9cd6325a30194900c66"}, + "reactor": {:hex, :reactor, "0.17.0", "eb8bdb530dbae824e2d36a8538f8ec4f3aa7c2d1b61b04959fa787c634f88b49", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3c3bf71693adbad9117b11ec83cfed7d5851b916ade508ed9718de7ae165bf25"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.3.4", "3fe37fdfa01e3f7c9f4ced16b7b9950d5bfbb7fab024148e1968b0460ce1336b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c0293d41461f1c1f1774379c668a240b008f4f8dcd550ea82b06163a55dcd53b"}, + "spark": {:hex, :spark, "2.3.5", "f30d30ecc3b4ab9b932d9aada66af7677fc1f297a2c349b0bcec3eafb9f996e8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0e9d339704d5d148f77f2b2fef3bcfc873a9e9bb4224fcf289c545d65827202f"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, @@ -53,6 +53,6 @@ "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, - "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"}, "ymlr": {:hex, :ymlr, "5.1.4", "b924d61e1fc1ec371cde6ab3ccd9311110b1e052fc5c2460fb322e8380e7712a", [:mix], [], "hexpm", "75f16cf0709fbd911b30311a0359a7aa4b5476346c01882addefd5f2b1cfaa51"}, } From 14581be77761b62f26597f8f0648540523fef96e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:19:22 -0400 Subject: [PATCH 1181/1215] chore(deps-dev): bump the dev-dependencies group with 4 updates (#624) Bumps the dev-dependencies group with 4 updates: [dialyxir](https://github.com/jeremyjh/dialyxir), [ecto_dev_logger](https://github.com/fuelen/ecto_dev_logger), [ex_doc](https://github.com/elixir-lang/ex_doc) and [git_ops](https://github.com/zachdaniel/git_ops). Updates `dialyxir` from 1.4.5 to 1.4.6 - [Release notes](https://github.com/jeremyjh/dialyxir/releases) - [Changelog](https://github.com/jeremyjh/dialyxir/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeremyjh/dialyxir/compare/1.4.5...1.4.6) Updates `ecto_dev_logger` from 0.14.1 to 0.15.0 - [Release notes](https://github.com/fuelen/ecto_dev_logger/releases) - [Commits](https://github.com/fuelen/ecto_dev_logger/compare/v0.14.1...v0.15.0) Updates `ex_doc` from 0.38.2 to 0.38.4 - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.38.2...v0.38.4) Updates `git_ops` from 2.8.0 to 2.9.0 - [Changelog](https://github.com/zachdaniel/git_ops/blob/master/CHANGELOG.md) - [Commits](https://github.com/zachdaniel/git_ops/compare/v2.8.0...v2.9.0) --- updated-dependencies: - dependency-name: dialyxir dependency-version: 1.4.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: ecto_dev_logger dependency-version: 0.15.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: ex_doc dependency-version: 0.38.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: git_ops dependency-version: 2.9.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 726124db..be72f892 100644 --- a/mix.lock +++ b/mix.lock @@ -7,20 +7,20 @@ "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, - "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.14.1", "af385ce1af1c4210ad67a4c46b985c370713446a179144a1da2885138c9fb242", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "14a64ebae728b3c45db6ba8bb185979c8e01fc1b0d3d1d9c01c7a2b798e8c698"}, + "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.15.0", "df5a997ffb17dca9011556857a0f5b7d8cd53ca7c452ef98828664b6e48d4400", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "b2c807d7d599a4fcf288139851c09262333b193bdb41f8d65f515853d117e88a"}, "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, - "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, + "git_ops": {:hex, :git_ops, "2.9.0", "b74f6040084f523055b720cc7ef718da47f2cbe726a5f30c2871118635cb91c1", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "7fdf84be3490e5692c5dc1f8a1084eed47a221c1063e41938c73312f0bfea259"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "igniter": {:hex, :igniter, "0.6.30", "83a466369ebb8fe009e0823c7bf04314dc545122c2d48f896172fc79df33e99d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "76a14d5b7f850bb03b5243088c3649d54a2e52e34a2aa1104dee23cf50a8bae0"}, From 5c4a429b0f404bd8ef5d803b80ff462c129cf807 Mon Sep 17 00:00:00 2001 From: Steve Brambilla Date: Thu, 2 Oct 2025 23:09:32 -0400 Subject: [PATCH 1182/1215] improvement: Add immutable version of `ash_raise_error` function to support extensions like Citus (#620) --- lib/extensions/immutable_raise_error.ex | 74 +++++++++++++++++++++++++ lib/repo.ex | 11 +++- lib/sql_implementation.ex | 5 ++ mix.exs | 4 +- mix.lock | 2 +- 5 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 lib/extensions/immutable_raise_error.ex diff --git a/lib/extensions/immutable_raise_error.ex b/lib/extensions/immutable_raise_error.ex new file mode 100644 index 00000000..7cda113d --- /dev/null +++ b/lib/extensions/immutable_raise_error.ex @@ -0,0 +1,74 @@ +defmodule AshPostgres.Extensions.ImmutableRaiseError do + @moduledoc """ + An extension that installs an immutable version of ash_raise_error. + + This can be used to improve compatibility with Postgres sharding extensions like Citus, + which requires functions used in CASE or COALESCE expressions to be immutable. + + The new `ash_raise_error_immutable` functions add an additional row-dependent argument to ensure + the planner doesn't constant-fold error expressions. + + To install, add this module to your repo's `installed_extensions` list: + + ```elixir + def installed_extensions do + ["ash-functions", AshPostgres.Extensions.ImmutableRaiseError] + end + ``` + + And run `mix ash_postgres.generate_migrations` to generate the migrations. + + Once installed, you can control whether the immutable function is used by adding this to your + repo: + + ```elixir + def immutable_expr_error?, do: true + ``` + """ + + use AshPostgres.CustomExtension, name: "immutable_raise_error", latest_version: 1 + + @impl true + def install(0) do + ash_raise_error_immutable() + end + + @impl true + def uninstall(_version) do + "execute(\"DROP FUNCTION IF EXISTS ash_raise_error_immutable(jsonb, ANYCOMPATIBLE), ash_raise_error_immutable(jsonb, ANYELEMENT, ANYCOMPATIBLE)\")" + end + + defp ash_raise_error_immutable do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, token ANYCOMPATIBLE) + RETURNS BOOLEAN AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + -- 'token' is intentionally ignored; its presence makes the call non-constant at the call site. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql + IMMUTABLE + SET search_path = ''; + \"\"\") + + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, type_signal ANYELEMENT, token ANYCOMPATIBLE) + RETURNS ANYELEMENT AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + -- 'token' is intentionally ignored; its presence makes the call non-constant at the call site. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql + IMMUTABLE + SET search_path = ''; + \"\"\") + """ + end +end diff --git a/lib/repo.ex b/lib/repo.ex index fb7cb7eb..e1b0726d 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -113,6 +113,13 @@ defmodule AshPostgres.Repo do @doc "Disable expression errors for this repo" @callback disable_expr_error?() :: boolean + @doc """ + Opt-in to using immutable versions of the expression error functions. + + Requires the `AshPostgres.Extensions.ImmutableRaiseError` extension. + """ + @callback immutable_expr_error?() :: boolean + defmacro __using__(opts) do quote bind_quoted: [opts: opts] do if Keyword.get(opts, :define_ecto_repo?, true) do @@ -145,6 +152,7 @@ defmodule AshPostgres.Repo do def drop?, do: true def disable_atomic_actions?, do: false def disable_expr_error?, do: false + def immutable_expr_error?, do: false # default to false in 4.0 def prefer_transaction?, do: true @@ -315,7 +323,8 @@ defmodule AshPostgres.Repo do create?: 0, drop?: 0, disable_atomic_actions?: 0, - disable_expr_error?: 0 + disable_expr_error?: 0, + immutable_expr_error?: 0 # We do this switch because `!@warn_on_missing_ash_functions` in the function body triggers # a dialyzer error diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 99d80f8f..fefdf876 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -334,4 +334,9 @@ defmodule AshPostgres.SqlImplementation do {types, new_returns || returns} end + + @impl true + def immutable_errors?(repo) do + repo.immutable_expr_error?() + end end diff --git a/mix.exs b/mix.exs index 00778fec..c06ca801 100644 --- a/mix.exs +++ b/mix.exs @@ -168,7 +168,9 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.5 and >= 3.5.35")}, {:spark, "~> 2.3 and >= 2.3.4"}, - {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.90")}, + # TODO: bump to next ash_sql release + # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.90")}, + {:ash_sql, git: "https://github.com/ash-project/ash_sql.git"}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index be72f892..7cd067b4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.43", "222f9a8ac26ad3b029f8e69306cc83091c992d858b4538af12e33a148f301cab", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "48b2aa274c524f5b968c563dd56aec8f9b278c529c8aa46e6fe0ca564c26cc1c"}, - "ash_sql": {:hex, :ash_sql, "0.3.0", "2c43ddcc8c7fb51dc25ba3bca965d8b68e7aaecb290cabfed3cf213965aca937", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ef03759d6a1d4cb189fcadbd183cf047f0565d7d88ea8612d85c9e6e724835e7"}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "65854408e7ce129f78fabafb0a4393f0142da6a6", []}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From 6a2675cd8865cb5d3b38dc052e98b944a7355282 Mon Sep 17 00:00:00 2001 From: Steve Brambilla Date: Fri, 3 Oct 2025 09:52:31 -0400 Subject: [PATCH 1183/1215] test: add repro for ash_sql issue (#629) --- .../test_repo/rsvps/20251002180954.json | 43 +++++++++++ .../20251002180954_migrate_resources62.exs | 20 +++++ test/support/domain.ex | 1 + test/support/resources/rsvp.ex | 42 +++++++++++ test/support/types/response.ex | 75 +++++++++++++++++++ test/type_test.exs | 9 +++ 6 files changed, 190 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/rsvps/20251002180954.json create mode 100644 priv/test_repo/migrations/20251002180954_migrate_resources62.exs create mode 100644 test/support/resources/rsvp.ex create mode 100644 test/support/types/response.ex diff --git a/priv/resource_snapshots/test_repo/rsvps/20251002180954.json b/priv/resource_snapshots/test_repo/rsvps/20251002180954.json new file mode 100644 index 00000000..31e6d18f --- /dev/null +++ b/priv/resource_snapshots/test_repo/rsvps/20251002180954.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "0", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "response", + "type": "integer" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "8ADC3A631361A18B1B9A2070D5E8477428EDE39DE3B43FA5FF8E50CE7710B9E5", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "rsvps" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20251002180954_migrate_resources62.exs b/priv/test_repo/migrations/20251002180954_migrate_resources62.exs new file mode 100644 index 00000000..fe90b42c --- /dev/null +++ b/priv/test_repo/migrations/20251002180954_migrate_resources62.exs @@ -0,0 +1,20 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources62 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 + create table(:rsvps, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:response, :integer, null: false, default: 0) + end + end + + def down do + drop(table(:rsvps)) + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index 2bf55a0b..ec992cbe 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -53,6 +53,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Order) resource(AshPostgres.Test.Chat) resource(AshPostgres.Test.Message) + resource(AshPostgres.Test.RSVP) end authorization do diff --git a/test/support/resources/rsvp.ex b/test/support/resources/rsvp.ex new file mode 100644 index 00000000..965f053d --- /dev/null +++ b/test/support/resources/rsvp.ex @@ -0,0 +1,42 @@ +defmodule AshPostgres.Test.RSVP do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "rsvps" + repo AshPostgres.TestRepo + end + + actions do + default_accept(:*) + defaults([:create, :read, :update, :destroy]) + + # Uses an expression with an array of atoms for a custom type backed by integers. + update :clear_response do + change( + atomic_update( + :response, + expr( + if response in [:accepted, :declined] do + :awaiting + else + response + end + ) + ) + ) + end + end + + attributes do + uuid_primary_key(:id) + + attribute(:response, AshPostgres.Test.Types.Response, + allow_nil?: false, + public?: true, + default: 0 + ) + end +end diff --git a/test/support/types/response.ex b/test/support/types/response.ex new file mode 100644 index 00000000..51f475e0 --- /dev/null +++ b/test/support/types/response.ex @@ -0,0 +1,75 @@ +defmodule AshPostgres.Test.Types.Response do + @moduledoc false + use Ash.Type + use AshPostgres.Type + require Ash.Expr + + @atoms_to_ints %{accepted: 1, declined: 2, awaiting: 0} + @ints_to_atoms Map.new(@atoms_to_ints, fn {k, v} -> {v, k} end) + @atom_values Map.keys(@atoms_to_ints) + @string_values Enum.map(@atom_values, &to_string/1) + + @impl Ash.Type + def storage_type, do: :integer + + @impl Ash.Type + def cast_input(nil, _), do: {:ok, nil} + + def cast_input(value, _) when value in @atom_values, do: {:ok, value} + def cast_input(value, _) when value in @string_values, do: {:ok, String.to_existing_atom(value)} + + def cast_input(integer, _) when is_integer(integer), + do: Map.fetch(@ints_to_atoms, integer) + + def cast_input(_, _), do: :error + + @impl Ash.Type + def matches_type?(value, _) when is_atom(value) and value in @atom_values, do: true + def matches_type?(_, _), do: false + + @impl Ash.Type + def cast_stored(nil, _), do: {:ok, nil} + def cast_stored(integer, _) when is_integer(integer), do: Map.fetch(@ints_to_atoms, integer) + def cast_stored(_, _), do: :error + + @impl Ash.Type + def dump_to_native(nil, _), do: {:ok, nil} + def dump_to_native(atom, _) when is_atom(atom), do: Map.fetch(@atoms_to_ints, atom) + def dump_to_native(_, _), do: :error + + @impl Ash.Type + def cast_atomic(new_value, constraints) do + if Ash.Expr.expr?(new_value) do + {:atomic, new_value} + else + case cast_input(new_value, constraints) do + {:ok, value} -> {:atomic, value} + {:error, error} -> {:error, error} + end + end + end + + @impl Ash.Type + def apply_atomic_constraints(new_value, _constraints) do + {:ok, + Ash.Expr.expr( + if ^new_value in ^@atom_values do + ^new_value + else + error( + Ash.Error.Changes.InvalidChanges, + message: "must be one of %{values}", + vars: %{values: ^Enum.join(@atom_values, ", ")} + ) + end + )} + end + + @impl AshPostgres.Type + def value_to_postgres_default(_, _, value) do + case Map.fetch(@atoms_to_ints, value) do + {:ok, integer} -> {:ok, Integer.to_string(integer)} + :error -> :error + end + end +end diff --git a/test/type_test.exs b/test/type_test.exs index 4c3731e7..6355fe92 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -1,6 +1,7 @@ defmodule AshPostgres.Test.TypeTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post + alias AshPostgres.Test.RSVP require Ash.Query @@ -107,4 +108,12 @@ defmodule AshPostgres.Test.TypeTest do post = Ash.Query.for_read(Post, :with_version_check, version: 1) |> Ash.read!() refute is_nil(post) end + + test "array expressions work with custom types that map atoms to integers" do + rsvp = RSVP |> Ash.Changeset.for_create(:create, %{response: :accepted}) |> Ash.create!() + + updated = rsvp |> Ash.Changeset.for_update(:clear_response, %{}) |> Ash.update!() + + assert updated.response == :awaiting + end end From c9a6e3e7b580361274fdd7102b9dbe23bb1223a6 Mon Sep 17 00:00:00 2001 From: Alexandre Moreau Date: Sat, 4 Oct 2025 22:33:37 +0200 Subject: [PATCH 1184/1215] test:add failing test (#627) --- .../20251001120813.json | 124 ++++++++++++++++++ .../20251001120813_migrate_resources7.exs | 78 +++++++++++ test/multitenancy_test.exs | 20 ++- test/support/multitenancy/domain.ex | 1 + .../non_multitenant_post_multitenant_link.ex | 52 ++++++++ test/support/multitenancy/resources/post.ex | 11 ++ 6 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json create mode 100644 priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs create mode 100644 test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex diff --git a/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json new file mode 100644 index 00000000..16e8af1a --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json @@ -0,0 +1,124 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "\"active\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "state", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "name": "non_multitenant_post_multitenant_links_source_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "multitenant_posts" + }, + "scale": null, + "size": null, + "source": "source_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": true, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "non_multitenant_post_multitenant_links_dest_id_fkey", + "on_delete": "delete", + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "posts" + }, + "scale": null, + "size": null, + "source": "dest_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "F19D22A316D43A64BF2F1E052F545346BD0BCBC660E8EF559D1D4D73D8969A4D", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "non_multitenant_post_multitenant_links_unique_link_index", + "keys": [ + { + "type": "atom", + "value": "source_id" + }, + { + "type": "atom", + "value": "dest_id" + } + ], + "name": "unique_link", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": false, + "strategy": "context" + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "non_multitenant_post_multitenant_links" +} \ No newline at end of file diff --git a/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs new file mode 100644 index 00000000..42a6f5a3 --- /dev/null +++ b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs @@ -0,0 +1,78 @@ +defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources7 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 + create table(:non_multitenant_post_multitenant_links, primary_key: false, prefix: prefix()) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:state, :text, default: "active") + + add( + :source_id, + references(:multitenant_posts, + column: :id, + name: "non_multitenant_post_multitenant_links_source_id_fkey", + type: :uuid, + prefix: prefix(), + on_delete: :delete_all + ), + null: false + ) + + add( + :dest_id, + references(:posts, + column: :id, + name: "non_multitenant_post_multitenant_links_dest_id_fkey", + type: :uuid, + prefix: "public", + on_delete: :delete_all + ), + null: false + ) + end + + create(index(:non_multitenant_post_multitenant_links, [:source_id])) + + create(index(:non_multitenant_post_multitenant_links, [:dest_id])) + + create( + unique_index(:non_multitenant_post_multitenant_links, [:source_id, :dest_id], + name: "non_multitenant_post_multitenant_links_unique_link_index" + ) + ) + end + + def down do + drop_if_exists( + unique_index(:non_multitenant_post_multitenant_links, [:source_id, :dest_id], + name: "non_multitenant_post_multitenant_links_unique_link_index" + ) + ) + + drop_if_exists(index(:non_multitenant_post_multitenant_links, [:dest_id])) + + drop_if_exists(index(:non_multitenant_post_multitenant_links, [:source_id])) + + drop( + constraint( + :non_multitenant_post_multitenant_links, + "non_multitenant_post_multitenant_links_source_id_fkey" + ) + ) + + drop( + constraint( + :non_multitenant_post_multitenant_links, + "non_multitenant_post_multitenant_links_dest_id_fkey" + ) + ) + + drop(table(:non_multitenant_post_multitenant_links, prefix: prefix())) + end +end diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 0dd99520..89034434 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -2,7 +2,7 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false require Ash.Query - alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User} + alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User, NonMultitenantPostMultitenantLink} alias AshPostgres.Test.Post, as: GlobalPost setup do @@ -226,6 +226,24 @@ defmodule AshPostgres.Test.MultitenancyTest do ) end + test "loading non multitenant resource across a many_to_many works", %{org1: org1} do + post = Post + |> Ash.Changeset.for_create(:create, %{name: "foo"}) + |> Ash.Changeset.set_tenant(org1) + |> Ash.create!() + + GlobalPost + |> Ash.Changeset.for_create(:create, %{title: "fred"}) + |> Ash.create!() + + NonMultitenantPostMultitenantLink + |> Ash.Changeset.for_create(:create, %{source_id: post.id, dest_id: global_post.id}, tenant: org1) + |> Ash.create!() + + post |> Ash.load!([:linked_non_multitenant_posts_through_multitenant_link], tenant: org1) |> IO.inspect() + end + + test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do org = Org |> Ash.Changeset.new() |> Ash.create!() user = User |> Ash.Changeset.new() |> Ash.create!() diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 52ffbc63..6c1db197 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -12,6 +12,7 @@ defmodule AshPostgres.MultitenancyTest.Domain do resource(AshPostgres.MultitenancyTest.NonMultitenantPostLink) resource(AshPostgres.MultitenancyTest.CrossTenantPostLink) resource(AshPostgres.MultitenancyTest.CompositeKeyPost) + resource(AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink) end authorization do diff --git a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex new file mode 100644 index 00000000..4dff4d41 --- /dev/null +++ b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex @@ -0,0 +1,52 @@ +defmodule AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.MultitenancyTest.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "non_multitenant_post_multitenant_links" + repo AshPostgres.TestRepo + + references do + reference :source, on_delete: :delete, index?: true + reference :dest, on_delete: :delete, index?: true + end + end + + multitenancy do + strategy(:context) + end + + actions do + default_accept(:*) + + defaults([:create, :read, :update, :destroy]) + end + + identities do + identity(:unique_link, [:source_id, :dest_id]) + end + + attributes do + uuid_primary_key :id + + attribute :state, :atom do + public?(true) + constraints(one_of: [:active, :archived]) + default(:active) + end + end + + relationships do + belongs_to :source, AshPostgres.MultitenancyTest.Post do + public? true + allow_nil? false + end + + belongs_to :dest, AshPostgres.Test.Post do + public? true + allow_nil? false + end + end +end diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index e097fffb..b3358361 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -59,12 +59,23 @@ defmodule AshPostgres.MultitenancyTest.Post do # has_many(:non_multitenant_post_links, AshPostgres.MultitenancyTest.NonMultitenantPostLink) + has_many :non_multitenant_post_multitenant_links, AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do + destination_attribute :source_id + end + many_to_many :linked_non_multitenant_posts, AshPostgres.Test.Post do through(AshPostgres.MultitenancyTest.NonMultitenantPostLink) join_relationship(:non_multitenant_post_links) source_attribute_on_join_resource(:source_id) destination_attribute_on_join_resource(:dest_id) end + + many_to_many :linked_non_multitenant_posts_through_multitenant_link, AshPostgres.Test.Post do + through(AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink) + join_relationship(:non_multitenant_post_links_through_multitenant_link) + source_attribute_on_join_resource(:source_id) + destination_attribute_on_join_resource(:dest_id) + end end calculations do From 7ad0b86d145d4760882248d463222b742a872741 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 4 Oct 2025 16:53:54 -0400 Subject: [PATCH 1185/1215] fix: ensure that tenant is properly used in many-to-many joins --- lib/data_layer.ex | 6 +++-- mix.exs | 3 +-- test/multitenancy_test.exs | 27 ++++++++++++++----- .../non_multitenant_post_multitenant_link.ex | 10 +++---- test/support/multitenancy/resources/post.ex | 5 ++-- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 43b8f5a1..d9b2f41a 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1247,7 +1247,9 @@ defmodule AshPostgres.DataLayer do through_query = Ecto.Query.exclude(through_query, :select) through_query = - if through_query.joins && through_query.joins != [] do + if (through_query.joins && through_query.joins != []) || + (Ash.Resource.Info.multitenancy_strategy(relationship.through) == :context && + source_query.tenant) do subquery( set_subquery_prefix( through_query, @@ -1256,7 +1258,7 @@ defmodule AshPostgres.DataLayer do ) ) else - through_query + set_subquery_prefix(through_query, source_query, relationship.through) end if query.__ash_bindings__[:__order__?] do diff --git a/mix.exs b/mix.exs index c06ca801..cdc61840 100644 --- a/mix.exs +++ b/mix.exs @@ -169,8 +169,7 @@ defmodule AshPostgres.MixProject do {:ash, ash_version("~> 3.5 and >= 3.5.35")}, {:spark, "~> 2.3 and >= 2.3.4"}, # TODO: bump to next ash_sql release - # {:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.90")}, - {:ash_sql, git: "https://github.com/ash-project/ash_sql.git"}, + {:ash_sql, ash_sql_version(git: "https://github.com/ash-project/ash_sql.git")}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 89034434..6253e46f 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -2,7 +2,16 @@ defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false require Ash.Query - alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User, NonMultitenantPostMultitenantLink} + + alias AshPostgres.MultitenancyTest.{ + CompositeKeyPost, + NamedOrg, + Org, + Post, + User, + NonMultitenantPostMultitenantLink + } + alias AshPostgres.Test.Post, as: GlobalPost setup do @@ -227,23 +236,27 @@ defmodule AshPostgres.Test.MultitenancyTest do end test "loading non multitenant resource across a many_to_many works", %{org1: org1} do - post = Post + post = + Post |> Ash.Changeset.for_create(:create, %{name: "foo"}) |> Ash.Changeset.set_tenant(org1) |> Ash.create!() - GlobalPost + global_post = + GlobalPost |> Ash.Changeset.for_create(:create, %{title: "fred"}) |> Ash.create!() NonMultitenantPostMultitenantLink - |> Ash.Changeset.for_create(:create, %{source_id: post.id, dest_id: global_post.id}, tenant: org1) - |> Ash.create!() + |> Ash.Changeset.for_create(:create, %{source_id: post.id, dest_id: global_post.id}, + tenant: org1 + ) + |> Ash.create!() - post |> Ash.load!([:linked_non_multitenant_posts_through_multitenant_link], tenant: org1) |> IO.inspect() + post + |> Ash.load!([:linked_non_multitenant_posts_through_multitenant_link], tenant: org1) end - test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do org = Org |> Ash.Changeset.new() |> Ash.create!() user = User |> Ash.Changeset.new() |> Ash.create!() diff --git a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex index 4dff4d41..56dd8d26 100644 --- a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex +++ b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex @@ -29,7 +29,7 @@ defmodule AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do end attributes do - uuid_primary_key :id + uuid_primary_key(:id) attribute :state, :atom do public?(true) @@ -40,13 +40,13 @@ defmodule AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do relationships do belongs_to :source, AshPostgres.MultitenancyTest.Post do - public? true - allow_nil? false + public?(true) + allow_nil?(false) end belongs_to :dest, AshPostgres.Test.Post do - public? true - allow_nil? false + public?(true) + allow_nil?(false) end end end diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index b3358361..6f9a943a 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -59,8 +59,9 @@ defmodule AshPostgres.MultitenancyTest.Post do # has_many(:non_multitenant_post_links, AshPostgres.MultitenancyTest.NonMultitenantPostLink) - has_many :non_multitenant_post_multitenant_links, AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do - destination_attribute :source_id + has_many :non_multitenant_post_multitenant_links, + AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do + destination_attribute(:source_id) end many_to_many :linked_non_multitenant_posts, AshPostgres.Test.Post do From 588b1cbd28a15a3a19fd1cc59a3ff78a65ddd715 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 4 Oct 2025 17:00:47 -0400 Subject: [PATCH 1186/1215] chore: credo --- test/multitenancy_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 6253e46f..85a97cbe 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -6,10 +6,10 @@ defmodule AshPostgres.Test.MultitenancyTest do alias AshPostgres.MultitenancyTest.{ CompositeKeyPost, NamedOrg, + NonMultitenantPostMultitenantLink, Org, Post, - User, - NonMultitenantPostMultitenantLink + User } alias AshPostgres.Test.Post, as: GlobalPost From 7d4d4a4f879cae82c363635179717367584e8c9b Mon Sep 17 00:00:00 2001 From: Frank Polasek Dugan III Date: Sun, 5 Oct 2025 14:41:42 -0500 Subject: [PATCH 1187/1215] chore: remove deprecated script documentation; fix mise confusion in .tool-versions (#630) * chore: remove deprecated script * chore: fix mise confusion in .tool-versions --- .tool-versions | 2 +- .../development/migrations-and-tasks.md | 29 ------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/.tool-versions b/.tool-versions index 32823875..6c947239 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 27.1.2 -elixir 1.18.4 \ No newline at end of file +elixir 1.18.4-otp-27 diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 5fd26271..592bb9a9 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -23,35 +23,6 @@ For more information on generating migrations, run `mix help ash_postgres.genera > > If you have are using schema-based multitenancy, you will also need to define a `all_tenants/0` function in your repo module. See `AshPostgres.Repo` for more. -### Regenerating Migrations - -Often, you will run into a situation where you want to make a slight change to a resource after you've already generated and run migrations. If you are using git and would like to undo those changes, then regenerate the migrations, this script may prove useful: - -```bash -#!/bin/bash - -# Get count of untracked migrations -N_MIGRATIONS=$(git ls-files --others priv/repo/migrations | wc -l) - -# Rollback untracked migrations -mix ash_postgres.rollback -n $N_MIGRATIONS - -# Delete untracked migrations and snapshots -git ls-files --others priv/repo/migrations | xargs rm -git ls-files --others priv/resource_snapshots | xargs rm - -# Regenerate migrations -mix ash.codegen --name $1 - -# Run migrations if flag -if echo $* | grep -e "-m" -q -then - mix ash.migrate -fi -``` - -After saving this file to something like `regen.sh`, make it executable with `chmod +x regen.sh`. Now you can run it with `./regen.sh name_of_operation`. If you would like the migrations to automatically run after regeneration, add the `-m` flag: `./regen.sh name_of_operation -m`. - ## Running Migrations in Production Define a module similar to the following: From c45b3366f95c3419da576eaadc7f37860d5873a9 Mon Sep 17 00:00:00 2001 From: Elliot Bowes Date: Sun, 5 Oct 2025 22:45:54 +0100 Subject: [PATCH 1188/1215] fix: Support non-public PostgreSQL schemas in resource generator (#631) - Add schema field to generated resources when table is in non-public schema - Fix SQL queries to use schema-qualified table names for foreign keys and constraints - Update index queries to respect actual schema instead of hardcoded 'public' - Add test coverage for tables in custom schemas with foreign keys and indexes * fix: guard against missing snapshot directories in migration generator Fixes crash when generating migrations for resources in non-public schemas where the snapshot directory doesn't exist yet. * chore: run mix format --- .../migration_generator.ex | 19 ++- lib/resource_generator/resource_generator.ex | 7 + lib/resource_generator/spec.ex | 16 ++- test/resource_generator_test.exs | 130 ++++++++++++++++++ 4 files changed, 161 insertions(+), 11 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index fcb44c95..00276e25 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -649,14 +649,19 @@ defmodule AshPostgres.MigrationGenerator do folder = get_snapshot_folder(snapshot, opts) snapshot_path = get_snapshot_path(snapshot, folder) - snapshot_path - |> File.ls!() - |> Enum.filter(&String.contains?(&1, "_dev.json")) - |> Enum.each(fn snapshot_name -> + # Guard against missing directories - can happen for new resources or when + # get_snapshot_path's fallback logic returns a non-existent path for + # resources in non-public schemas + if File.dir?(snapshot_path) do snapshot_path - |> Path.join(snapshot_name) - |> File.rm!() - end) + |> File.ls!() + |> Enum.filter(&String.contains?(&1, "_dev.json")) + |> Enum.each(fn snapshot_name -> + snapshot_path + |> Path.join(snapshot_name) + |> File.rm!() + end) + end end) end diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 99da8386..b3a507b7 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -115,6 +115,7 @@ if Code.ensure_loaded?(Igniter) do postgres do table #{inspect(table_spec.table_name)} repo #{inspect(table_spec.repo)} + #{schema_option(table_spec)} #{no_migrate_flag} #{references(table_spec, opts[:no_migrations])} #{custom_indexes(table_spec, opts[:no_migrations])} @@ -144,6 +145,12 @@ if Code.ensure_loaded?(Igniter) do end) end + defp schema_option(%{schema: schema}) when schema != "public" do + "schema #{inspect(schema)}" + end + + defp schema_option(_), do: "" + defp default_actions(opts) do cond do opts[:default_actions] && opts[:public] -> diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index f2da7ec5..9997bbac 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -122,7 +122,13 @@ defmodule AshPostgres.ResourceGenerator.Spec do result end + defp qualified_table_name(%{schema: schema, table_name: table_name}) do + "#{schema}.#{table_name}" + end + defp add_foreign_keys(spec) do + qualified_table = qualified_table_name(spec) + %Postgrex.Result{rows: fkey_rows} = spec.repo.query!( """ @@ -178,7 +184,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do constraints.update_rule, constraints.delete_rule """, - [spec.table_name, spec.schema], + [qualified_table, spec.schema], log: false ) @@ -218,6 +224,8 @@ defmodule AshPostgres.ResourceGenerator.Spec do end defp add_check_constraints(spec) do + qualified_table = qualified_table_name(spec) + %Postgrex.Result{rows: check_constraint_rows} = spec.repo.query!( """ @@ -230,7 +238,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do contype = 'c' AND conrelid::regclass::text = $1 """, - [spec.table_name], + [qualified_table], log: false ) @@ -278,7 +286,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do LEFT JOIN pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p' JOIN - pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary + pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = $2 JOIN information_schema.tables ta ON ta.table_name = t.relname WHERE @@ -312,7 +320,7 @@ defmodule AshPostgres.ResourceGenerator.Spec do LEFT JOIN pg_constraint c ON c.conindid = ix.indexrelid AND c.contype = 'p' JOIN - pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = 'public' -- Adjust schema name if necessary + pg_indexes idx ON idx.indexname = i.relname AND idx.schemaname = $2 JOIN information_schema.tables ta ON ta.table_name = t.relname WHERE diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index c50292aa..4878cf6d 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -62,4 +62,134 @@ defmodule AshPostgres.ResourceGeenratorTests do end """) end + + test "a resource is generated from a table in a non-public schema with foreign keys and indexes" do + AshPostgres.TestRepo.query!("CREATE SCHEMA IF NOT EXISTS inventory") + + AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS inventory.products CASCADE") + AshPostgres.TestRepo.query!("DROP TABLE IF EXISTS inventory.warehouses CASCADE") + + AshPostgres.TestRepo.query!(""" + CREATE TABLE inventory.warehouses ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + name VARCHAR(255) NOT NULL, + location VARCHAR(255) + ) + """) + + AshPostgres.TestRepo.query!("CREATE INDEX warehouses_name_idx ON inventory.warehouses(name)") + + AshPostgres.TestRepo.query!(""" + CREATE TABLE inventory.products ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + name VARCHAR(255) NOT NULL, + warehouse_id UUID REFERENCES inventory.warehouses(id) ON DELETE CASCADE, + quantity INTEGER + ) + """) + + AshPostgres.TestRepo.query!( + "CREATE INDEX products_warehouse_id_idx ON inventory.products(warehouse_id)" + ) + + test_project() + |> Igniter.compose_task("ash_postgres.gen.resources", [ + "MyApp.Inventory", + "--tables", + "inventory.warehouses,inventory.products", + "--yes", + "--repo", + "AshPostgres.TestRepo" + ]) + |> assert_creates("lib/my_app/inventory/warehouse.ex", """ + defmodule MyApp.Inventory.Warehouse do + use Ash.Resource, + domain: MyApp.Inventory, + data_layer: AshPostgres.DataLayer + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + postgres do + table("warehouses") + repo(AshPostgres.TestRepo) + schema("inventory") + end + + attributes do + uuid_primary_key :id do + public?(true) + end + + attribute :name, :string do + allow_nil?(false) + public?(true) + end + + attribute :location, :string do + public?(true) + end + end + + relationships do + has_many :products, MyApp.Inventory.Product do + public?(true) + end + end + end + """) + |> assert_creates("lib/my_app/inventory/product.ex", """ + defmodule MyApp.Inventory.Product do + use Ash.Resource, + domain: MyApp.Inventory, + data_layer: AshPostgres.DataLayer + + actions do + defaults([:read, :destroy, create: :*, update: :*]) + end + + postgres do + table("products") + repo(AshPostgres.TestRepo) + schema("inventory") + + references do + reference :warehouse do + on_delete(:delete) + end + end + end + + attributes do + uuid_primary_key :id do + public?(true) + end + + uuid_primary_key :id do + public?(true) + end + + attribute :name, :string do + public?(true) + end + + attribute :name, :string do + allow_nil?(false) + public?(true) + end + + attribute :quantity, :integer do + public?(true) + end + end + + relationships do + belongs_to :warehouse, MyApp.Inventory.Warehouse do + public?(true) + end + end + end + """) + end end From cf0d1df59476c263002aeae37d90a3ea21ae4c87 Mon Sep 17 00:00:00 2001 From: Steve Brambilla Date: Wed, 8 Oct 2025 23:18:05 -0400 Subject: [PATCH 1189/1215] refactor: move immutable error expr from AshSql into AshPostgres (#633) --- lib/extensions/immutable_raise_error.ex | 155 ++++++++++++++++++++++++ lib/sql_implementation.ex | 30 ++++- mix.exs | 1 - mix.lock | 2 +- 4 files changed, 181 insertions(+), 7 deletions(-) diff --git a/lib/extensions/immutable_raise_error.ex b/lib/extensions/immutable_raise_error.ex index 7cda113d..b7ccf347 100644 --- a/lib/extensions/immutable_raise_error.ex +++ b/lib/extensions/immutable_raise_error.ex @@ -28,6 +28,8 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do use AshPostgres.CustomExtension, name: "immutable_raise_error", latest_version: 1 + require Ecto.Query + @impl true def install(0) do ash_raise_error_immutable() @@ -71,4 +73,157 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do \"\"\") """ end + + @doc false + def immutable_error_expr( + query, + %Ash.Query.Function.Error{arguments: [exception, input]} = value, + bindings, + embedded?, + acc, + type + ) do + acc = %{acc | has_error?: true} + + {encoded, acc} = + if Ash.Expr.expr?(input) do + frag_parts = + Enum.flat_map(input, fn {key, value} -> + if Ash.Expr.expr?(value) do + [ + expr: to_string(key), + raw: "::text, ", + expr: value, + raw: ", " + ] + else + [ + expr: to_string(key), + raw: "::text, ", + expr: value, + raw: "::jsonb, " + ] + end + end) + + frag_parts = + List.update_at(frag_parts, -1, fn {:raw, text} -> + {:raw, String.trim_trailing(text, ", ") <> "))"} + end) + + AshSql.Expr.dynamic_expr( + query, + %Ash.Query.Function.Fragment{ + embedded?: false, + arguments: + [ + raw: "jsonb_build_object('exception', ", + expr: inspect(exception), + raw: "::text, 'input', jsonb_build_object(" + ] ++ + frag_parts + }, + bindings, + embedded?, + nil, + acc + ) + else + {Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}), acc} + end + + dynamic_type = + if type do + # This is a type hint, if we're raising an error, we tell it what the value + # type *would* be in this expression so that we can return a "NULL" of that type + # its weird, but there isn't any other way that I can tell :) + AshSql.Expr.validate_type!(query, type, value) + + type = + AshSql.Expr.parameterized_type( + bindings.sql_behaviour, + type, + [], + :expr + ) + + Ecto.Query.dynamic(type(fragment("NULL"), ^type)) + else + nil + end + + case {dynamic_type, immutable_error_expr_token(query, bindings)} do + {_, nil} -> + :error + + {nil, row_token} -> + {:ok, + Ecto.Query.dynamic( + fragment("ash_raise_error_immutable(?::jsonb, ?)", ^encoded, ^row_token) + ), acc} + + {dynamic_type, row_token} -> + {:ok, + Ecto.Query.dynamic( + fragment( + "ash_raise_error_immutable(?::jsonb, ?, ?)", + ^encoded, + ^dynamic_type, + ^row_token + ) + ), acc} + end + end + + # Returns a row-dependent token to prevent constant-folding for immutable functions. + defp immutable_error_expr_token(query, bindings) do + resource = query.__ash_bindings__.resource + ref_binding = bindings.root_binding + + pk_attr_names = Ash.Resource.Info.primary_key(resource) + + attr_names = + case pk_attr_names do + [] -> + case Ash.Resource.Info.attributes(resource) do + [%{name: name} | _] -> [name] + _ -> [] + end + + pk -> + pk + end + + if ref_binding && attr_names != [] do + value_exprs = + Enum.map(attr_names, fn attr_name -> + if bindings[:parent?] && + ref_binding not in List.wrap(bindings[:lateral_join_bindings]) do + Ecto.Query.dynamic(field(parent_as(^ref_binding), ^attr_name)) + else + Ecto.Query.dynamic(field(as(^ref_binding), ^attr_name)) + end + end) + + row_parts = + value_exprs + |> Enum.map(&{:casted_expr, &1}) + |> Enum.intersperse({:raw, ", "}) + + {%Ecto.Query.DynamicExpr{} = token, _acc} = + AshSql.Expr.dynamic_expr( + query, + %Ash.Query.Function.Fragment{ + embedded?: false, + arguments: [raw: "ROW("] ++ row_parts ++ [raw: ")"] + }, + AshSql.Expr.set_location(bindings, :sub_expr), + false + ) + + token + else + nil + end + end end diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index fefdf876..71b7f03d 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -200,6 +200,31 @@ defmodule AshPostgres.SqlImplementation do end end + def expr( + query, + %Ash.Query.Function.Error{} = value, + bindings, + embedded?, + acc, + type + ) do + resource = query.__ash_bindings__.resource + repo = AshSql.dynamic_repo(resource, AshPostgres.SqlImplementation, query) + + if repo.immutable_expr_error?() do + AshPostgres.Extensions.ImmutableRaiseError.immutable_error_expr( + query, + value, + bindings, + embedded?, + acc, + type + ) + else + :error + end + end + def expr( _query, _expr, @@ -334,9 +359,4 @@ defmodule AshPostgres.SqlImplementation do {types, new_returns || returns} end - - @impl true - def immutable_errors?(repo) do - repo.immutable_expr_error?() - end end diff --git a/mix.exs b/mix.exs index cdc61840..79848b88 100644 --- a/mix.exs +++ b/mix.exs @@ -168,7 +168,6 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.5 and >= 3.5.35")}, {:spark, "~> 2.3 and >= 2.3.4"}, - # TODO: bump to next ash_sql release {:ash_sql, ash_sql_version(git: "https://github.com/ash-project/ash_sql.git")}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index 7cd067b4..f5d12abd 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.43", "222f9a8ac26ad3b029f8e69306cc83091c992d858b4538af12e33a148f301cab", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "48b2aa274c524f5b968c563dd56aec8f9b278c529c8aa46e6fe0ca564c26cc1c"}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "65854408e7ce129f78fabafb0a4393f0142da6a6", []}, + "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "3044c0555dbe6733d16868951ee89e6d5ef336fa", []}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From 84e528cbf7d43655dec367350d7b020e88fc38f8 Mon Sep 17 00:00:00 2001 From: Daniel Gollings Date: Thu, 9 Oct 2025 06:01:51 +0200 Subject: [PATCH 1190/1215] fix: update ash_postgresql to handle the new bulk_create response in Ash v3.5.44 (#632) --- lib/data_layer.ex | 33 ++++++-- test/bulk_create_test.exs | 147 +++++++++++++++++++++++++++++++++ test/support/resources/post.ex | 50 +++++++++++ 3 files changed, 225 insertions(+), 5 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index d9b2f41a..aaa2738f 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2047,11 +2047,18 @@ defmodule AshPostgres.DataLayer do maybe_create_tenant!(resource, result) end - Ash.Resource.put_metadata( - result, - :bulk_create_index, - changeset.context.bulk_create.index - ) + case get_bulk_operation_metadata(changeset, :bulk_create) do + {index, metadata_key} -> + Ash.Resource.put_metadata(result, metadata_key, index) + + nil -> + # Compatibility fallback + Ash.Resource.put_metadata( + result, + :bulk_create_index, + changeset.context[:bulk_create][:index] + ) + end end)} end end @@ -3638,4 +3645,20 @@ defmodule AshPostgres.DataLayer do resource end end + + defp get_bulk_operation_metadata(changeset, bulk_action_type) do + changeset.context + |> Enum.find_value(fn + # New format: {{:bulk_create, ref}, value} -> {index, metadata_key} + {{^bulk_action_type, ref}, value} -> + {value.index, {:"#{bulk_action_type}_index", ref}} + + # Fallback for old format: {:bulk_create, value} -> {index, metadata_key} + {^bulk_action_type, value} when is_map(value) -> + {value.index, :"#{bulk_action_type}_index"} + + _ -> + nil + end) + end end diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index 01769ccb..d6fd8c17 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -2,6 +2,7 @@ defmodule AshPostgres.BulkCreateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Post, Record} + require Ash.Query import Ash.Expr describe "bulk creates" do @@ -355,4 +356,150 @@ defmodule AshPostgres.BulkCreateTest do |> Ash.read!() end end + + describe "nested bulk operations" do + test "supports bulk_create in after_action callbacks" do + result = + Ash.bulk_create!( + [%{title: "trigger_nested"}], + Post, + :create_with_nested_bulk_create, + return_records?: true, + authorize?: false + ) + + # Assert the bulk result contains the expected data + assert %Ash.BulkResult{records: [original_post]} = result + assert original_post.title == "trigger_nested" + + # Verify all posts that should exist after the nested operation + all_posts = + Post + |> Ash.Query.sort(:title) + |> Ash.read!() + + # Should have: 1 original + 2 nested = 3 total posts + assert length(all_posts) == 3 + + # Verify we have the expected posts with correct titles + post_titles = Enum.map(all_posts, & &1.title) |> Enum.sort() + assert post_titles == ["nested_post_1", "nested_post_2", "trigger_nested"] + + # Verify the specific nested posts were created by the after_action callback + nested_posts = + Post + |> Ash.Query.filter(expr(title in ["nested_post_1", "nested_post_2"])) + |> Ash.Query.sort(:title) + |> Ash.read!() + + assert length(nested_posts) == 2 + assert [%{title: "nested_post_1"}, %{title: "nested_post_2"}] = nested_posts + + # Verify that each nested post has proper metadata + Enum.each(nested_posts, fn post -> + assert is_binary(post.id) + assert post.title in ["nested_post_1", "nested_post_2"] + end) + end + + test "supports bulk_update in after_action callbacks" do + # Create the original post - the after_action callback will create and update additional posts + result = + Ash.bulk_create!( + [%{title: "trigger_nested_update"}], + Post, + :create_with_nested_bulk_update, + return_records?: true, + authorize?: false + ) + + # Assert the bulk result contains the expected data + assert %Ash.BulkResult{records: [original_post]} = result + assert original_post.title == "trigger_nested_update" + + # Verify all posts that should exist after the nested operations + # The after_action callback should have created 2 posts and updated them + all_posts = + Post + |> Ash.Query.sort(:title) + |> Ash.read!() + + # Should have: 1 original + 2 created and updated = 3 total posts + assert length(all_posts) == 3 + + # Verify the original post still exists + original_posts = + Post + |> Ash.Query.filter(expr(title == "trigger_nested_update")) + |> Ash.read!() + + assert length(original_posts) == 1 + assert hd(original_posts).title == "trigger_nested_update" + + # Verify the nested posts were created and then updated by the after_action callback + updated_posts = + Post + |> Ash.Query.filter(expr(title == "updated_via_nested_bulk")) + |> Ash.read!() + + assert length(updated_posts) == 2 + + # Verify that the updated posts have proper metadata and were actually updated + Enum.each(updated_posts, fn post -> + assert is_binary(post.id) + assert post.title == "updated_via_nested_bulk" + end) + + # Verify no posts remain with the intermediate titles (they should have been updated) + intermediate_posts = + Post + |> Ash.Query.filter(expr(title in ["post_to_update_1", "post_to_update_2"])) + |> Ash.read!() + + assert intermediate_posts == [], + "Posts should have been updated, not left with intermediate titles" + end + + test "nested bulk operations handle metadata indexing correctly" do + # Create multiple posts in the parent bulk operation to test indexing + # Each parent post's after_action callback will create nested posts + result = + Ash.bulk_create!( + [ + %{title: "trigger_nested"}, + %{title: "trigger_nested_2"} + ], + Post, + :create_with_nested_bulk_create, + return_records?: true, + authorize?: false + ) + + # Assert both parent posts were created + assert %Ash.BulkResult{records: parent_posts} = result + assert length(parent_posts) == 2 + + parent_titles = Enum.map(parent_posts, & &1.title) |> Enum.sort() + assert parent_titles == ["trigger_nested", "trigger_nested_2"] + + # Verify total posts: 2 parent + (2 nested per parent from after_action) = 6 total + all_posts = Post |> Ash.Query.sort(:title) |> Ash.read!() + assert length(all_posts) == 6 + + # Count posts by type + nested_posts = + Post + |> Ash.Query.filter(expr(title in ["nested_post_1", "nested_post_2"])) + |> Ash.read!() + + # Should have 4 nested posts (2 for each parent operation via after_action callbacks) + assert length(nested_posts) == 4 + + # Verify each nested post has proper structure + Enum.each(nested_posts, fn post -> + assert is_binary(post.id) + assert post.title in ["nested_post_1", "nested_post_2"] + end) + end + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 817e0ff9..69791a38 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -432,6 +432,56 @@ defmodule AshPostgres.Test.Post do upsert_fields([:price]) end + create :create_with_nested_bulk_create do + change( + after_action(fn changeset, result, context -> + Ash.bulk_create!( + [%{title: "nested_post_1"}, %{title: "nested_post_2"}], + __MODULE__, + :create, + authorize?: false, + tenant: changeset.tenant, + return_records?: true + ) + + {:ok, result} + end) + ) + end + + create :create_with_nested_bulk_update do + change( + after_action(fn changeset, result, context -> + created_posts = + Ash.bulk_create!( + [%{title: "post_to_update_1"}, %{title: "post_to_update_2"}], + __MODULE__, + :create, + authorize?: false, + tenant: changeset.tenant, + return_records?: true + ) + + post_ids = Enum.map(created_posts.records, & &1.id) + + Ash.bulk_update!( + __MODULE__, + :set_title, + %{title: "updated_via_nested_bulk"}, + filter: [id: [in: post_ids]], + authorize?: false, + tenant: changeset.tenant + ) + + {:ok, result} + end) + ) + end + + update :set_title do + accept([:title]) + end + update :set_title_from_author do change(atomic_update(:title, expr(author.first_name))) end From a2aac8ae84547f2f828383052abcb4f7e89ad459 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 9 Oct 2025 23:38:09 -0400 Subject: [PATCH 1191/1215] chore: update ash_sql fix: simplify bulk operation metadata handling --- lib/data_layer.ex | 12 ++++++------ mix.exs | 2 +- mix.lock | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index aaa2738f..6e3354d8 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2047,7 +2047,7 @@ defmodule AshPostgres.DataLayer do maybe_create_tenant!(resource, result) end - case get_bulk_operation_metadata(changeset, :bulk_create) do + case get_bulk_operation_metadata(changeset) do {index, metadata_key} -> Ash.Resource.put_metadata(result, metadata_key, index) @@ -3646,16 +3646,16 @@ defmodule AshPostgres.DataLayer do end end - defp get_bulk_operation_metadata(changeset, bulk_action_type) do + defp get_bulk_operation_metadata(changeset) do changeset.context |> Enum.find_value(fn # New format: {{:bulk_create, ref}, value} -> {index, metadata_key} - {{^bulk_action_type, ref}, value} -> - {value.index, {:"#{bulk_action_type}_index", ref}} + {{:bulk_create, ref}, value} -> + {value.index, {:bulk_create_index, ref}} # Fallback for old format: {:bulk_create, value} -> {index, metadata_key} - {^bulk_action_type, value} when is_map(value) -> - {value.index, :"#{bulk_action_type}_index"} + {:bulk_create, value} when is_map(value) -> + {value.index, :bulk_create_index} _ -> nil diff --git a/mix.exs b/mix.exs index 79848b88..260de0d3 100644 --- a/mix.exs +++ b/mix.exs @@ -168,7 +168,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.5 and >= 3.5.35")}, {:spark, "~> 2.3 and >= 2.3.4"}, - {:ash_sql, ash_sql_version(git: "https://github.com/ash-project/ash_sql.git")}, + {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.2")}, {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index f5d12abd..0c876d7c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.43", "222f9a8ac26ad3b029f8e69306cc83091c992d858b4538af12e33a148f301cab", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "48b2aa274c524f5b968c563dd56aec8f9b278c529c8aa46e6fe0ca564c26cc1c"}, - "ash_sql": {:git, "https://github.com/ash-project/ash_sql.git", "3044c0555dbe6733d16868951ee89e6d5ef336fa", []}, + "ash_sql": {:hex, :ash_sql, "0.3.1", "10c6b69d5b860d1162733324d249624399ade42ecfaff17573617a00f09eb66a", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a6f69c8709afd6581cfebf4713b90be8e63c530bde162b2691da850599da4a2c"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -40,7 +40,7 @@ "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, "reactor": {:hex, :reactor, "0.17.0", "eb8bdb530dbae824e2d36a8538f8ec4f3aa7c2d1b61b04959fa787c634f88b49", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3c3bf71693adbad9117b11ec83cfed7d5851b916ade508ed9718de7ae165bf25"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, - "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, + "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, From 9aaebc60f0f8015d8344aa7fc1ab2deaf5900d86 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 9 Oct 2025 23:38:56 -0400 Subject: [PATCH 1192/1215] chore: update ash_sql --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 0c876d7c..1827dcb4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.5.43", "222f9a8ac26ad3b029f8e69306cc83091c992d858b4538af12e33a148f301cab", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "48b2aa274c524f5b968c563dd56aec8f9b278c529c8aa46e6fe0ca564c26cc1c"}, - "ash_sql": {:hex, :ash_sql, "0.3.1", "10c6b69d5b860d1162733324d249624399ade42ecfaff17573617a00f09eb66a", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "a6f69c8709afd6581cfebf4713b90be8e63c530bde162b2691da850599da4a2c"}, + "ash_sql": {:hex, :ash_sql, "0.3.2", "e2d65dac1c813cbd2569a750bf1c063109778e840052e44535ced294d7638a19", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1f6e5d827c0eb55fc5a07f58eb97f9bb3e6b290d83df75883f422537b98c9c68"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, From c50052fde2aae223e2eb7949b7c5b869f3eb2da4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 9 Oct 2025 23:39:09 -0400 Subject: [PATCH 1193/1215] chore: release version v2.6.21 --- CHANGELOG.md | 21 +++++++++++++++++++++ mix.exs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6546f3a..2778d41d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.21](https://github.com/ash-project/ash_postgres/compare/v2.6.20...v2.6.21) (2025-10-10) + + + + +### Bug Fixes: + +* simplify bulk operation metadata handling by Zach Daniel + +* update ash_postgresql to handle the new bulk_create response in Ash v3.5.44 (#632) by Daniel Gollings + +* Support non-public PostgreSQL schemas in resource generator (#631) by Elliot Bowes + +* guard against missing snapshot directories in migration generator by Elliot Bowes + +* ensure that tenant is properly used in many-to-many joins by Zach Daniel + +### Improvements: + +* Add immutable version of `ash_raise_error` function to support extensions like Citus (#620) by Steve Brambilla + ## [v2.6.20](https://github.com/ash-project/ash_postgres/compare/v2.6.19...v2.6.20) (2025-09-27) diff --git a/mix.exs b/mix.exs index 260de0d3..f1d82380 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.20" + @version "2.6.21" def project do [ From e43b89f4381ac4dcf6e4fbe5cfc5a2a7db270b9d Mon Sep 17 00:00:00 2001 From: James Harton Date: Sat, 11 Oct 2025 12:51:55 +1300 Subject: [PATCH 1194/1215] chore: REUSE compliance (#635) --- .check.exs | 7 ++++++- .credo.exs | 4 ++++ .formatter.exs | 4 ++++ .github/dependabot.yml | 4 ++++ .github/workflows/elixir.yml | 6 +++++- .gitignore | 4 ++++ .tool-versions | 1 + .tool-versions.license | 3 +++ .vscode/settings.json.license | 3 +++ CHANGELOG.md | 6 ++++++ LICENSE | 21 ------------------- LICENSES/MIT.txt | 18 ++++++++++++++++ README.md | 7 +++++++ SECURITY.md | 6 ++++++ benchmarks/bulk_create.exs | 4 ++++ config/config.exs | 4 ++++ documentation/1.0-CHANGELOG.md | 6 ++++++ .../dsls/DSL-AshPostgres.DataLayer.md.license | 3 +++ .../what-is-ash-postgres.md | 6 ++++++ documentation/topics/advanced/expressions.md | 6 ++++++ .../topics/advanced/manual-relationships.md | 6 ++++++ .../advanced/schema-based-multitenancy.md | 6 ++++++ .../topics/advanced/using-multiple-repos.md | 6 ++++++ .../development/migrations-and-tasks.md | 6 ++++++ documentation/topics/development/testing.md | 6 ++++++ .../topics/development/upgrading-to-2.0.md | 6 ++++++ .../topics/resources/polymorphic-resources.md | 6 ++++++ documentation/topics/resources/references.md | 6 ++++++ .../get-started-with-ash-postgres.md | 6 ++++++ .../set-up-with-existing-database.md | 6 ++++++ lib/ash_postgres.ex | 4 ++++ lib/check_constraint.ex | 4 ++++ lib/custom_aggregate.ex | 4 ++++ lib/custom_extension.ex | 4 ++++ lib/custom_index.ex | 4 ++++ lib/data_layer.ex | 4 ++++ lib/data_layer/info.ex | 4 ++++ lib/ecto_migration_default.ex | 4 ++++ lib/extensions/immutable_raise_error.ex | 4 ++++ lib/extensions/vector.ex | 4 ++++ lib/functions/binding.ex | 4 ++++ lib/functions/ilike.ex | 4 ++++ lib/functions/like.ex | 4 ++++ lib/functions/trigram_similarity.ex | 4 ++++ lib/functions/vector_cosine_distance.ex | 4 ++++ lib/functions/vector_l2_distance.ex | 4 ++++ lib/igniter.ex | 4 ++++ lib/manual_relationship.ex | 4 ++++ lib/migration.ex | 4 ++++ lib/migration_compile_cache.ex | 4 ++++ lib/migration_generator/ash_functions.ex | 4 ++++ .../migration_generator.ex | 4 ++++ lib/migration_generator/operation.ex | 4 ++++ lib/migration_generator/phase.ex | 4 ++++ lib/mix/helpers.ex | 4 ++++ lib/mix/tasks/ash_postgres.create.ex | 4 ++++ lib/mix/tasks/ash_postgres.drop.ex | 4 ++++ lib/mix/tasks/ash_postgres.gen.resources.ex | 4 ++++ .../tasks/ash_postgres.generate_migrations.ex | 4 ++++ lib/mix/tasks/ash_postgres.install.ex | 4 ++++ lib/mix/tasks/ash_postgres.migrate.ex | 4 ++++ lib/mix/tasks/ash_postgres.rollback.ex | 4 ++++ lib/mix/tasks/ash_postgres.setup_vector.ex | 4 ++++ .../tasks/ash_postgres.squash_snapshots.ex | 4 ++++ lib/multitenancy.ex | 4 ++++ lib/reference.ex | 4 ++++ lib/repo.ex | 4 ++++ lib/repo/before_compile.ex | 4 ++++ lib/resource_generator/resource_generator.ex | 4 ++++ lib/resource_generator/sensitive_data.ex | 4 ++++ lib/resource_generator/spec.ex | 4 ++++ lib/sql_implementation.ex | 4 ++++ lib/statement.ex | 4 ++++ lib/type.ex | 4 ++++ lib/types/ci_string_wrapper.ex | 4 ++++ lib/types/ltree.ex | 4 ++++ lib/types/string_wrapper.ex | 4 ++++ lib/types/timestamptz.ex | 4 ++++ lib/types/timestamptz_usec.ex | 4 ++++ lib/types/tsquery.ex | 4 ++++ lib/types/tsvector.ex | 4 ++++ lib/verifiers/ensure_table_or_polymorphic.ex | 4 ++++ ...te_multitenancy_and_non_full_match_type.ex | 4 ++++ ...event_multidimensional_array_aggregates.ex | 4 ++++ .../validate_identity_index_names.ex | 4 ++++ lib/verifiers/validate_references.ex | 4 ++++ lib/version_agent.ex | 3 +++ logos/small-logo.png.license | 3 +++ mix.exs | 17 ++++++++++++--- mix.lock.license | 3 +++ ...6214825_migrate_resources_extensions_1.exs | 4 ++++ .../20250526214827_migrate_resources1.exs | 4 ++++ .../dev_test_repo/extensions.json.license | 3 +++ .../20250526214827.json.license | 3 +++ .../extensions.json.license | 3 +++ .../accounts/20221217123726.json.license | 3 +++ .../accounts/20240327211150.json.license | 3 +++ .../authors/20220805191443.json.license | 3 +++ .../authors/20220914104733.json.license | 3 +++ .../authors/20240327211150.json.license | 3 +++ .../authors/20240705113722.json.license | 3 +++ .../authors/20250908212414.json.license | 3 +++ .../chats/20250908093505.json.license | 3 +++ .../20241208221219.json.license | 3 +++ .../comedians/20241217232254.json.license | 3 +++ .../comedians/20250413141328.json.license | 3 +++ .../comment_links/20250123161002.json.license | 3 +++ .../20220805191443.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../comments/20220805191443.json.license | 3 +++ .../comments/20240327211150.json.license | 3 +++ .../comments/20240327211917.json.license | 3 +++ .../20230816231942.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20231116013020.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20240327211917.json.license | 3 +++ .../20231116013020.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20240327211917.json.license | 3 +++ .../20230816231942.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20240327211917.json.license | 3 +++ .../20250714225304.json.license | 3 +++ .../20250714225304.json.license | 3 +++ .../20230816231942.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../content/20250123164209.json.license | 3 +++ .../20250123164209.json.license | 3 +++ .../test_repo/csv/20250320225052.json.license | 3 +++ .../customers/20250908073737.json.license | 3 +++ .../entities/20240109160153.json.license | 3 +++ .../entities/20240327211150.json.license | 3 +++ .../entities/20240327211917.json.license | 3 +++ .../test_repo/extensions.json.license | 3 +++ .../integer_posts/20220805191443.json.license | 3 +++ .../items/20240713134055.json.license | 3 +++ .../items/20240717104854.json.license | 3 +++ .../items/20240717153736.json.license | 3 +++ .../jokes/20241217232254.json.license | 3 +++ .../jokes/20250413141328.json.license | 3 +++ .../managers/20230526144249.json.license | 3 +++ .../managers/20240327211150.json.license | 3 +++ .../messages/20250908093505.json.license | 3 +++ .../20250519103535.json.license | 3 +++ .../20220805191443.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20240627223225.json.license | 3 +++ .../20240702164513.json.license | 3 +++ .../20240703155134.json.license | 3 +++ .../20250122190558.json.license | 3 +++ .../note/20250123164209.json.license | 3 +++ .../orders/20250908073737.json.license | 3 +++ .../orgs/20230129050950.json.license | 3 +++ .../orgs/20240327211150.json.license | 3 +++ .../orgs/20250210191116.json.license | 3 +++ .../other_items/20240713134055.json.license | 3 +++ .../other_items/20240717151815.json.license | 3 +++ .../points/20250313112823.json.license | 3 +++ .../20240227180858.json.license | 3 +++ .../20240227181137.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20240516205244.json.license | 3 +++ .../20240517223946.json.license | 3 +++ .../post_links/20220805191443.json.license | 3 +++ .../post_links/20221017133955.json.license | 3 +++ .../post_links/20221202194704.json.license | 3 +++ .../post_links/20240610195853.json.license | 3 +++ .../post_links/20240617193218.json.license | 3 +++ .../20240906170759.json.license | 3 +++ .../post_ratings/20220805191443.json.license | 3 +++ .../post_ratings/20240327211150.json.license | 3 +++ .../post_tags/20250810102512.json.license | 3 +++ .../post_views/20230905050351.json.license | 3 +++ .../post_views/20240327211917.json.license | 3 +++ .../posts/20220805191443.json.license | 3 +++ .../posts/20221125171150.json.license | 3 +++ .../posts/20221125171204.json.license | 3 +++ .../posts/20230129050950.json.license | 3 +++ .../posts/20230823161017.json.license | 3 +++ .../posts/20231127215636.json.license | 3 +++ .../posts/20231129141453.json.license | 3 +++ .../posts/20231219132807.json.license | 3 +++ .../posts/20240129221511.json.license | 3 +++ .../posts/20240224001913.json.license | 3 +++ .../posts/20240327211150.json.license | 3 +++ .../posts/20240327211917.json.license | 3 +++ .../posts/20240503012410.json.license | 3 +++ .../posts/20240504185511.json.license | 3 +++ .../posts/20240524031113.json.license | 3 +++ .../posts/20240524041750.json.license | 3 +++ .../posts/20240617193218.json.license | 3 +++ .../posts/20240618102809.json.license | 3 +++ .../posts/20240712232026.json.license | 3 +++ .../posts/20240715135403.json.license | 3 +++ .../posts/20240910180107.json.license | 3 +++ .../posts/20240911225320.json.license | 3 +++ .../posts/20240918104740.json.license | 3 +++ .../posts/20240929121224.json.license | 3 +++ .../posts/20250217054207.json.license | 3 +++ .../posts/20250313112823.json.license | 3 +++ .../posts/20250520130634.json.license | 3 +++ .../posts/20250521105654.json.license | 3 +++ .../posts/20250612113920.json.license | 3 +++ .../posts/20250618011917.json.license | 3 +++ .../products/20250908073737.json.license | 3 +++ .../profile/20220805191443.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../punchlines/20250413141328.json.license | 3 +++ .../records/20240109160153.json.license | 3 +++ .../records/20240327211150.json.license | 3 +++ .../records/20240327211917.json.license | 3 +++ .../20250605230457.json.license | 3 +++ .../20240717153736.json.license | 3 +++ .../rsvps/20251002180954.json.license | 3 +++ .../20240821213522.json.license | 3 +++ .../staff_group/20250123164209.json.license | 3 +++ .../20250123164209.json.license | 3 +++ .../standup_clubs/20250413141328.json.license | 3 +++ .../20240618085942.json.license | 3 +++ .../string_points/20250313112823.json.license | 3 +++ .../sub_items/20240713134055.json.license | 3 +++ .../20240130133933.json.license | 3 +++ .../20240130133933.json.license | 3 +++ .../20240130133933.json.license | 3 +++ .../20240130133933.json.license | 3 +++ .../tags/20250810102512.json.license | 3 +++ .../20240327211150.json.license | 3 +++ .../20240327211917.json.license | 3 +++ .../temp_entities/20240109160153.json.license | 3 +++ .../composite_key/20250220073135.json.license | 3 +++ .../composite_key/20250220073141.json.license | 3 +++ .../20250122203454.json.license | 3 +++ .../friend_links/20240610162043.json.license | 3 +++ .../20220805191441.json.license | 3 +++ .../20240327211149.json.license | 3 +++ .../20251001120813.json.license | 3 +++ .../20250731124648.json.license | 3 +++ .../20250731124648.json.license | 3 +++ .../20250731124648.json.license | 3 +++ .../20250731124648.json.license | 3 +++ .../user_invites/20240727145758.json.license | 3 +++ .../users/20220805191443.json.license | 3 +++ .../users/20221217123726.json.license | 3 +++ .../users/20230129050950.json.license | 3 +++ .../users/20240327211150.json.license | 3 +++ .../users/20240727145758.json.license | 3 +++ .../users/20240929124728.json.license | 3 +++ .../users/20250320225052.json.license | 3 +++ .../users/20250321142835.json.license | 3 +++ .../migrations/.gitkeep.license | 3 +++ .../20240627223224_install_5_extensions.exs | 4 ++++ ...2025_install_ash-functions_extension_4.exs | 4 ++++ ...3205301_migrate_resources_extensions_1.exs | 4 ++++ .../20220805191440_install_4_extensions.exs | 4 ++++ .../20220805191443_migrate_resources1.exs | 4 ++++ .../20220914104733_migrate_resources2.exs | 4 ++++ .../20221017133955_migrate_resources3.exs | 4 ++++ .../20221125171148_migrate_resources4.exs | 4 ++++ .../20221125171150_migrate_resources5.exs | 4 ++++ .../20221202194704_migrate_resources6.exs | 4 ++++ .../20221217123726_migrate_resources7.exs | 4 ++++ .../20230129050950_migrate_resources8.exs | 4 ++++ .../20230526144249_migrate_resources9.exs | 4 ++++ ...182523_install_ash-functions_extension.exs | 4 ++++ ...59_install_demo-functions_v0_extension.exs | 4 ++++ ...18_install_demo-functions_v1_extension.exs | 4 ++++ ...6231942_add_complex_calculation_tables.exs | 4 ++++ .../20230823161017_migrate_resources10.exs | 4 ++++ .../20230905050351_add_post_views.exs | 4 ++++ ...3020_add_complex_calculations_channels.exs | 4 ++++ .../20231127212608_add_composite_type.exs | 4 ++++ .../20231127215636_migrate_resources11.exs | 4 ++++ .../20231129141453_migrate_resources12.exs | 4 ++++ ...0937_install_ash-functions_extension_2.exs | 4 ++++ .../20231219132807_migrate_resources13.exs | 4 ++++ ...1611_install_ash-functions_extension_3.exs | 4 ++++ .../20240109155951_create_temp_schema.exs | 4 ++++ .../20240109160153_migrate_resources14.exs | 4 ++++ .../20240129221511_migrate_resources15.exs | 4 ++++ ...133933_add_resources_for_subquery_test.exs | 4 ++++ .../20240224001913_migrate_resources16.exs | 4 ++++ .../20240227180858_migrate_resources17.exs | 4 ++++ .../20240227181137_migrate_resources18.exs | 4 ++++ .../20240229050455_install_5_extensions.exs | 4 ++++ .../20240327211150_migrate_resources19.exs | 4 ++++ .../20240327211917_migrate_resources20.exs | 4 ++++ .../20240503012410_migrate_resources21.exs | 4 ++++ .../20240504185511_migrate_resources22.exs | 4 ++++ .../20240516205244_migrate_resources23.exs | 4 ++++ .../20240517223946_migrate_resources24.exs | 4 ++++ .../20240524031113_migrate_resources25.exs | 4 ++++ .../20240524041750_migrate_resources26.exs | 4 ++++ .../20240610195853_migrate_resources27.exs | 4 ++++ .../20240617193218_migrate_resources28.exs | 4 ++++ .../20240618085942_migrate_resources29.exs | 4 ++++ .../20240618102809_migrate_resources30.exs | 4 ++++ ...2715_install_ash-functions_extension_4.exs | 4 ++++ .../20240627223225_migrate_resources31.exs | 4 ++++ .../20240703155134_migrate_resources32.exs | 4 ++++ .../20240705113722_migrate_resources33.exs | 4 ++++ .../20240712232026_migrate_resources34.exs | 4 ++++ ...240713134055_multi_domain_calculations.exs | 4 ++++ .../20240715135403_migrate_resources35.exs | 4 ++++ ...7104854_no_attributes_calculation_test.exs | 4 ++++ .../20240717151815_migrate_resources36.exs | 4 ++++ .../20240717153736_migrate_resources37.exs | 4 ++++ .../20240727145758_user_invites.exs | 4 ++++ .../20240906170759_migrate_resources38.exs | 4 ++++ .../20240910180107_migrate_resources39.exs | 4 ++++ .../20240911225319_install_1_extensions.exs | 4 ++++ .../20240911225320_migrate_resources40.exs | 4 ++++ .../20240918104740_migrate_resources41.exs | 4 ++++ .../20240929121224_migrate_resources42.exs | 4 ++++ .../20240929124728_migrate_resources43.exs | 4 ++++ .../20241208221219_migrate_resources44.exs | 4 ++++ .../20241217232254_migrate_resources45.exs | 4 ++++ ...3205259_migrate_resources_extensions_1.exs | 4 ++++ .../20250122190558_migrate_resources46.exs | 4 ++++ .../20250123161002_migrate_resources47.exs | 4 ++++ .../20250123164209_migrate_resources48.exs | 4 ++++ .../20250210191116_migrate_resources49.exs | 4 ++++ .../20250217054207_migrate_resources50.exs | 4 ++++ .../20250313112823_migrate_resources51.exs | 4 ++++ .../20250320225052_add_csv_resource.exs | 4 ++++ .../20250321142835_migrate_resources52.exs | 4 ++++ ...41328_add_punchlines_and_standup_clubs.exs | 4 ++++ .../20250519103535_migrate_resources53.exs | 4 ++++ .../20250520130634_migrate_resources54.exs | 4 ++++ ...20250521105654_add_model_tuple_to_post.exs | 4 ++++ ...0457_create_record_temp_entities_table.exs | 4 ++++ .../20250612113920_migrate_resources55.exs | 4 ++++ .../20250618011917_migrate_resources56.exs | 4 ++++ ..._complex_calculations_folder_and_items.exs | 4 ++++ .../20250731124648_migrate_resources57.exs | 4 ++++ .../20250810102512_migrate_resources58.exs | 4 ++++ .../20250908073737_migrate_resources59.exs | 4 ++++ .../20250908093505_migrate_resources60.exs | 4 ++++ .../20250908212414_migrate_resources61.exs | 4 ++++ .../20251002180954_migrate_resources62.exs | 4 ++++ .../20220805191441_migrate_resources1.exs | 4 ++++ .../20240327211149_migrate_resources2.exs | 4 ++++ .../20240610162043_migrate_resources3.exs | 4 ++++ .../20250122203454_migrate_resources4.exs | 4 ++++ .../20250220073135_migrate_resources5.exs | 4 ++++ .../20250220073141_migrate_resources6.exs | 4 ++++ .../20251001120813_migrate_resources7.exs | 4 ++++ test/aggregate_test.exs | 4 ++++ test/ash_postgres_test.exs | 4 ++++ test/atomics_test.exs | 4 ++++ test/bulk_create_test.exs | 4 ++++ test/bulk_destroy_test.exs | 4 ++++ test/bulk_update_test.exs | 4 ++++ test/calculation_test.exs | 4 ++++ test/cascade_destroy_test.exs | 4 ++++ test/combination_test.exs | 4 ++++ test/complex_calculations_test.exs | 4 ++++ test/composite_type_test.exs | 4 ++++ test/constraint_test.exs | 4 ++++ test/create_test.exs | 4 ++++ test/custom_expression_test.exs | 4 ++++ test/custom_index_test.exs | 4 ++++ ...ic_non_bulk_actions_policy_bypass_test.exs | 4 ++++ test/destroy_test.exs | 4 ++++ test/dev_migrations_test.exs | 4 ++++ test/distinct_test.exs | 4 ++++ test/ecto_compatibility_test.exs | 4 ++++ test/embeddable_resource_test.exs | 4 ++++ test/enum_test.exs | 4 ++++ test/error_expr_test.exs | 4 ++++ ...lationship_by_parent_relationship_test.exs | 4 ++++ test/filter_field_policy_test.exs | 4 ++++ test/filter_test.exs | 4 ++++ test/load_test.exs | 4 ++++ test/lock_test.exs | 4 ++++ test/ltree_test.exs | 4 ++++ test/manual_relationships_test.exs | 4 ++++ test/manual_update_test.exs | 4 ++++ test/many_to_many_expr_test.exs | 4 ++++ test/migration_generator_test.exs | 4 ++++ test/mix/tasks/ash_postgres.install_test.exs | 4 ++++ test/mix_squash_snapshots_test.exs | 4 ++++ test/multi_domain_calculations_test.exs | 4 ++++ test/multitenancy_test.exs | 4 ++++ test/parent_filter_test.exs | 4 ++++ test/parent_sort_test.exs | 4 ++++ test/polymorphism_test.exs | 4 ++++ test/primary_key_test.exs | 4 ++++ test/references_test.exs | 4 ++++ test/rel_with_parent_filter_test.exs | 4 ++++ test/resource_generator_test.exs | 4 ++++ test/schema_test.exs | 4 ++++ test/select_test.exs | 4 ++++ test/sort_test.exs | 4 ++++ test/storage_types_test.exs | 4 ++++ test/subquery_test.exs | 4 ++++ test/support/complex_calculations/domain.ex | 4 ++++ .../resources/certification.ex | 4 ++++ .../complex_calculations/resources/channel.ex | 4 ++++ .../resources/channel_member.ex | 4 ++++ .../resources/dm_channel.ex | 4 ++++ .../resources/documentation.ex | 4 ++++ .../complex_calculations/resources/folder.ex | 4 ++++ .../resources/folder_item.ex | 4 ++++ .../complex_calculations/resources/skill.ex | 4 ++++ test/support/concat.ex | 4 ++++ test/support/dev_test_repo.ex | 4 ++++ test/support/domain.ex | 4 ++++ .../multi_domain_calculations/domain_one.ex | 4 ++++ .../domain_one/item.ex | 4 ++++ .../multi_domain_calculations/domain_three.ex | 4 ++++ .../domain_three/relationship_item.ex | 4 ++++ .../multi_domain_calculations/domain_two.ex | 4 ++++ .../domain_two/other_item.ex | 4 ++++ .../domain_two/sub_item.ex | 4 ++++ test/support/multitenancy/domain.ex | 4 ++++ .../resources/composite_key_post.ex | 4 ++++ .../resources/cross_tenant_post_link.ex | 4 ++++ .../resources/dev_migrations_org.ex | 4 ++++ .../multitenancy/resources/named_org.ex | 4 ++++ .../resources/non_multitenant_post_link.ex | 4 ++++ .../non_multitenant_post_multitenant_link.ex | 4 ++++ test/support/multitenancy/resources/org.ex | 4 ++++ test/support/multitenancy/resources/post.ex | 4 ++++ .../multitenancy/resources/post_link.ex | 4 ++++ test/support/multitenancy/resources/user.ex | 4 ++++ .../comments_containing_title.ex | 4 ++++ test/support/repo_case.ex | 4 ++++ test/support/resources/account.ex | 4 ++++ test/support/resources/author.ex | 4 ++++ test/support/resources/bio.ex | 4 ++++ test/support/resources/chat.ex | 4 ++++ test/support/resources/co_authored_post.ex | 4 ++++ test/support/resources/comedian.ex | 4 ++++ test/support/resources/comment.ex | 4 ++++ test/support/resources/comment_link.ex | 4 ++++ test/support/resources/content.ex | 4 ++++ .../resources/content_visibility_group.ex | 4 ++++ test/support/resources/csv.ex | 4 ++++ test/support/resources/customer.ex | 4 ++++ test/support/resources/db_point.ex | 4 ++++ test/support/resources/db_string_point.ex | 4 ++++ test/support/resources/entity.ex | 4 ++++ test/support/resources/integer_post.ex | 4 ++++ test/support/resources/invite.ex | 4 ++++ test/support/resources/joke.ex | 4 ++++ test/support/resources/manager.ex | 4 ++++ test/support/resources/message.ex | 4 ++++ test/support/resources/note.ex | 4 ++++ test/support/resources/order.ex | 4 ++++ test/support/resources/organization.ex | 4 ++++ test/support/resources/permalink.ex | 4 ++++ test/support/resources/post.ex | 4 ++++ test/support/resources/post_follower.ex | 4 ++++ test/support/resources/post_link.ex | 4 ++++ test/support/resources/post_tag.ex | 4 ++++ test/support/resources/post_views.ex | 4 ++++ .../resources/post_with_empty_update.ex | 4 ++++ test/support/resources/product.ex | 4 ++++ test/support/resources/profile.ex | 4 ++++ test/support/resources/punchline.ex | 4 ++++ test/support/resources/rating.ex | 4 ++++ test/support/resources/record.ex | 4 ++++ test/support/resources/record_temp_entity.ex | 4 ++++ test/support/resources/role.ex | 4 ++++ test/support/resources/rsvp.ex | 4 ++++ test/support/resources/settings.ex | 4 ++++ test/support/resources/staff_group.ex | 4 ++++ test/support/resources/staff_group_member.ex | 4 ++++ test/support/resources/standup_club.ex | 4 ++++ .../resources/stateful_post_follwer.ex | 4 ++++ test/support/resources/subquery/access.ex | 4 ++++ test/support/resources/subquery/child.ex | 4 ++++ .../resources/subquery/child_domain.ex | 4 ++++ test/support/resources/subquery/parent.ex | 4 ++++ .../resources/subquery/parent_domain.ex | 4 ++++ test/support/resources/subquery/through.ex | 4 ++++ test/support/resources/tag.ex | 4 ++++ test/support/resources/temp_entity.ex | 4 ++++ test/support/resources/user.ex | 4 ++++ test/support/string_agg.ex | 4 ++++ test/support/test_app.ex | 4 ++++ test/support/test_custom_extension.ex | 4 ++++ test/support/test_no_sandbox_repo.ex | 4 ++++ test/support/test_repo.ex | 4 ++++ test/support/trigram_word_similarity.ex | 4 ++++ test/support/types/composite_point.ex | 4 ++++ test/support/types/email.ex | 4 ++++ test/support/types/money.ex | 4 ++++ test/support/types/person_detail.ex | 4 ++++ test/support/types/point.ex | 4 ++++ test/support/types/response.ex | 4 ++++ test/support/types/status.ex | 4 ++++ test/support/types/status_enum.ex | 4 ++++ test/support/types/status_enum_no_cast.ex | 4 ++++ test/support/types/string_point.ex | 4 ++++ test/support/unrelated_aggregates/profile.ex | 4 ++++ test/support/unrelated_aggregates/report.ex | 4 ++++ .../unrelated_aggregates/secure_profile.ex | 4 ++++ test/support/unrelated_aggregates/user.ex | 4 ++++ test/test_helper.exs | 4 ++++ test/transaction_test.exs | 4 ++++ test/tuple_test.exs | 4 ++++ test/type_test.exs | 4 ++++ test/unique_identity_test.exs | 4 ++++ test/unrelated_aggregates_test.exs | 4 ++++ test/update_test.exs | 4 ++++ test/upsert_test.exs | 4 ++++ usage-rules.md | 6 ++++++ 509 files changed, 1926 insertions(+), 26 deletions(-) create mode 100644 .tool-versions.license create mode 100644 .vscode/settings.json.license delete mode 100644 LICENSE create mode 100644 LICENSES/MIT.txt create mode 100644 documentation/dsls/DSL-AshPostgres.DataLayer.md.license create mode 100644 logos/small-logo.png.license create mode 100644 mix.lock.license create mode 100644 priv/resource_snapshots/dev_test_repo/extensions.json.license create mode 100644 priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license create mode 100644 priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license create mode 100644 priv/resource_snapshots/test_repo/accounts/20221217123726.json.license create mode 100644 priv/resource_snapshots/test_repo/accounts/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/authors/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/authors/20220914104733.json.license create mode 100644 priv/resource_snapshots/test_repo/authors/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/authors/20240705113722.json.license create mode 100644 priv/resource_snapshots/test_repo/authors/20250908212414.json.license create mode 100644 priv/resource_snapshots/test_repo/chats/20250908093505.json.license create mode 100644 priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license create mode 100644 priv/resource_snapshots/test_repo/comedians/20241217232254.json.license create mode 100644 priv/resource_snapshots/test_repo/comedians/20250413141328.json.license create mode 100644 priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license create mode 100644 priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/comments/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/comments/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/comments/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license create mode 100644 priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/content/20250123164209.json.license create mode 100644 priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license create mode 100644 priv/resource_snapshots/test_repo/csv/20250320225052.json.license create mode 100644 priv/resource_snapshots/test_repo/customers/20250908073737.json.license create mode 100644 priv/resource_snapshots/test_repo/entities/20240109160153.json.license create mode 100644 priv/resource_snapshots/test_repo/entities/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/entities/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/extensions.json.license create mode 100644 priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/items/20240713134055.json.license create mode 100644 priv/resource_snapshots/test_repo/items/20240717104854.json.license create mode 100644 priv/resource_snapshots/test_repo/items/20240717153736.json.license create mode 100644 priv/resource_snapshots/test_repo/jokes/20241217232254.json.license create mode 100644 priv/resource_snapshots/test_repo/jokes/20250413141328.json.license create mode 100644 priv/resource_snapshots/test_repo/managers/20230526144249.json.license create mode 100644 priv/resource_snapshots/test_repo/managers/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/messages/20250908093505.json.license create mode 100644 priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license create mode 100644 priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license create mode 100644 priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license create mode 100644 priv/resource_snapshots/test_repo/note/20250123164209.json.license create mode 100644 priv/resource_snapshots/test_repo/orders/20250908073737.json.license create mode 100644 priv/resource_snapshots/test_repo/orgs/20230129050950.json.license create mode 100644 priv/resource_snapshots/test_repo/orgs/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/orgs/20250210191116.json.license create mode 100644 priv/resource_snapshots/test_repo/other_items/20240713134055.json.license create mode 100644 priv/resource_snapshots/test_repo/other_items/20240717151815.json.license create mode 100644 priv/resource_snapshots/test_repo/points/20250313112823.json.license create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license create mode 100644 priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license create mode 100644 priv/resource_snapshots/test_repo/post_links/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/post_links/20221017133955.json.license create mode 100644 priv/resource_snapshots/test_repo/post_links/20221202194704.json.license create mode 100644 priv/resource_snapshots/test_repo/post_links/20240610195853.json.license create mode 100644 priv/resource_snapshots/test_repo/post_links/20240617193218.json.license create mode 100644 priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license create mode 100644 priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license create mode 100644 priv/resource_snapshots/test_repo/post_views/20230905050351.json.license create mode 100644 priv/resource_snapshots/test_repo/post_views/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20221125171150.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20221125171204.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20230129050950.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20230823161017.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20231127215636.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20231129141453.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20231219132807.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240129221511.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240224001913.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240503012410.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240504185511.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240524031113.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240524041750.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240617193218.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240618102809.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240712232026.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240715135403.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240910180107.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240911225320.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240918104740.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20240929121224.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20250217054207.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20250313112823.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20250520130634.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20250521105654.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20250612113920.json.license create mode 100644 priv/resource_snapshots/test_repo/posts/20250618011917.json.license create mode 100644 priv/resource_snapshots/test_repo/products/20250908073737.json.license create mode 100644 priv/resource_snapshots/test_repo/profile/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license create mode 100644 priv/resource_snapshots/test_repo/records/20240109160153.json.license create mode 100644 priv/resource_snapshots/test_repo/records/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/records/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license create mode 100644 priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license create mode 100644 priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license create mode 100644 priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license create mode 100644 priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license create mode 100644 priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license create mode 100644 priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license create mode 100644 priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license create mode 100644 priv/resource_snapshots/test_repo/string_points/20250313112823.json.license create mode 100644 priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license create mode 100644 priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license create mode 100644 priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license create mode 100644 priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license create mode 100644 priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license create mode 100644 priv/resource_snapshots/test_repo/tags/20250810102512.json.license create mode 100644 priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license create mode 100644 priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license create mode 100644 priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license create mode 100644 priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license create mode 100644 priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license create mode 100644 priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license create mode 100644 priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license create mode 100644 priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20220805191443.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20221217123726.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20230129050950.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20240327211150.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20240727145758.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20240929124728.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20250320225052.json.license create mode 100644 priv/resource_snapshots/test_repo/users/20250321142835.json.license create mode 100644 priv/test_no_sandbox_repo/migrations/.gitkeep.license diff --git a/.check.exs b/.check.exs index 91e14cb1..cecfbc08 100644 --- a/.check.exs +++ b/.check.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + [ ## all available options with default values (see `mix check` docs for description) # parallel: true, @@ -12,7 +16,8 @@ # {:credo, "mix credo --format oneline"}, {:check_formatter, command: "mix spark.formatter --check"}, - {:check_migrations, command: "mix test.check_migrations"} + {:check_migrations, command: "mix test.check_migrations"}, + {:reuse, command: ["pipx", "run", "reuse", "lint", "-q"]} ## custom new tools may be added (mix tasks or arbitrary commands) # {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}}, # {:my_arbitrary_tool, command: "npm test", cd: "assets"}, diff --git a/.credo.exs b/.credo.exs index fa9affad..1e1ebe4c 100644 --- a/.credo.exs +++ b/.credo.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + # This file contains the configuration for Credo and you are probably reading # this after creating it with `mix credo.gen.config`. # diff --git a/.formatter.exs b/.formatter.exs index b9c6f498..07bd4f93 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + spark_locals_without_parens = [ all_tenants?: 1, base_filter_sql: 1, diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 597c8ec7..09b96595 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + --- updates: - directory: / diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 084df209..cf4e4897 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + name: CI on: push: @@ -19,6 +23,6 @@ jobs: publish-docs: ${{ matrix.postgres-version == '16' }} release: ${{ matrix.postgres-version == '16' }} igniter-upgrade: ${{matrix.postgres-version == '16'}} - + reuse: true secrets: hex_api_key: ${{ secrets.HEX_API_KEY }} diff --git a/.gitignore b/.gitignore index 7d849d6b..b16f7ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + # The directory Mix will write compiled artifacts to. /_build/ diff --git a/.tool-versions b/.tool-versions index 6c947239..0fa52d96 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,3 @@ erlang 27.1.2 elixir 1.18.4-otp-27 +pipx 1.8.0 diff --git a/.tool-versions.license b/.tool-versions.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/.tool-versions.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/.vscode/settings.json.license b/.vscode/settings.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/.vscode/settings.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/CHANGELOG.md b/CHANGELOG.md index 2778d41d..4b045424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ + + # Change Log All notable changes to this project will be documented in this file. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 4eb51a51..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Zachary Scott Daniel - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 00000000..d817195d --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 1bef9edc..98ba97ee 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ + + ![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-black-text.png?raw=true#gh-light-mode-only) ![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-white-text.png?raw=true#gh-dark-mode-only) @@ -5,6 +11,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/license/MIT) [![Hex version badge](https://img.shields.io/hexpm/v/ash_postgres.svg)](https://hex.pm/packages/ash_postgres) [![Hexdocs badge](https://img.shields.io/badge/docs-hexdocs-purple)](https://hexdocs.pm/ash_postgres) +[![REUSE status](https://api.reuse.software/badge/github.com/ash-project/ash_postgres)](https://api.reuse.software/info/github.com/ash-project/ash_postgres) # AshPostgres diff --git a/SECURITY.md b/SECURITY.md index 87d03b8b..8b1473d6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,9 @@ + + # Security Policy This is a copy of the security policy in the core Ash repo. That is the authoritative source. diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs index 99f235a1..5e665f19 100644 --- a/benchmarks/bulk_create.exs +++ b/benchmarks/bulk_create.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + alias AshPostgres.Test.{Domain, Post} AshPostgres.TestRepo.start_link() diff --git a/config/config.exs b/config/config.exs index 0041c0ac..5a179e32 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + import Config if Mix.env() == :dev do diff --git a/documentation/1.0-CHANGELOG.md b/documentation/1.0-CHANGELOG.md index 222e3265..0e65a587 100644 --- a/documentation/1.0-CHANGELOG.md +++ b/documentation/1.0-CHANGELOG.md @@ -1,3 +1,9 @@ + + ## [v2.0.0-rc.14](https://github.com/ash-project/ash_postgres/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-04-29) ### Improvements: diff --git a/documentation/dsls/DSL-AshPostgres.DataLayer.md.license b/documentation/dsls/DSL-AshPostgres.DataLayer.md.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/documentation/dsls/DSL-AshPostgres.DataLayer.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/documentation/topics/about-ash-postgres/what-is-ash-postgres.md b/documentation/topics/about-ash-postgres/what-is-ash-postgres.md index 883b3512..b88bac60 100644 --- a/documentation/topics/about-ash-postgres/what-is-ash-postgres.md +++ b/documentation/topics/about-ash-postgres/what-is-ash-postgres.md @@ -1,3 +1,9 @@ + + # What is AshPostgres? AshPostgres is the PostgreSQL `Ash.DataLayer` for [Ash Framework](https://hexdocs.pm/ash). This is the most fully-featured Ash data layer, and unless you need a specific characteristic or feature of another data layer, you should use `AshPostgres`. diff --git a/documentation/topics/advanced/expressions.md b/documentation/topics/advanced/expressions.md index 953e402d..c5a6a759 100644 --- a/documentation/topics/advanced/expressions.md +++ b/documentation/topics/advanced/expressions.md @@ -1,3 +1,9 @@ + + # Expressions In addition to the expressions listed in the [Ash expressions guide](https://hexdocs.pm/ash/expressions.html), AshPostgres provides the following expressions diff --git a/documentation/topics/advanced/manual-relationships.md b/documentation/topics/advanced/manual-relationships.md index a7adaac3..1bd438b5 100644 --- a/documentation/topics/advanced/manual-relationships.md +++ b/documentation/topics/advanced/manual-relationships.md @@ -1,3 +1,9 @@ + + # Manual Relationships See [Manual Relationships](https://hexdocs.pm/ash/relationships.html#manual-relationships) for an idea of manual relationships in general. diff --git a/documentation/topics/advanced/schema-based-multitenancy.md b/documentation/topics/advanced/schema-based-multitenancy.md index 69d7add1..f1459c1f 100644 --- a/documentation/topics/advanced/schema-based-multitenancy.md +++ b/documentation/topics/advanced/schema-based-multitenancy.md @@ -1,3 +1,9 @@ + + # Schema Based Multitenancy Multitenancy in AshPostgres is implemented via postgres schemas. For more information on schemas, see postgres' [schema documentation](https://www.postgresql.org/docs/current/ddl-schemas.html) diff --git a/documentation/topics/advanced/using-multiple-repos.md b/documentation/topics/advanced/using-multiple-repos.md index 3c85ff5c..02378413 100644 --- a/documentation/topics/advanced/using-multiple-repos.md +++ b/documentation/topics/advanced/using-multiple-repos.md @@ -1,3 +1,9 @@ + + # Using Multiple Repos When scaling PostgreSQL you may want to setup _read_ replicas to improve diff --git a/documentation/topics/development/migrations-and-tasks.md b/documentation/topics/development/migrations-and-tasks.md index 592bb9a9..2d163da4 100644 --- a/documentation/topics/development/migrations-and-tasks.md +++ b/documentation/topics/development/migrations-and-tasks.md @@ -1,3 +1,9 @@ + + # Migrations ## Tasks diff --git a/documentation/topics/development/testing.md b/documentation/topics/development/testing.md index 3fe7662b..eaf826f1 100644 --- a/documentation/topics/development/testing.md +++ b/documentation/topics/development/testing.md @@ -1,3 +1,9 @@ + + # Testing with AshPostgres When using AshPostgres resources in tests, you will likely want to include use a test case similar to the following. This will ensure that your repo runs everything in a transaction. diff --git a/documentation/topics/development/upgrading-to-2.0.md b/documentation/topics/development/upgrading-to-2.0.md index 34eeb78c..fd3e1cc7 100644 --- a/documentation/topics/development/upgrading-to-2.0.md +++ b/documentation/topics/development/upgrading-to-2.0.md @@ -1,3 +1,9 @@ + + # Upgrading to 2.0 There are only three breaking changes in this release, one of them is very significant, the other two are minor. diff --git a/documentation/topics/resources/polymorphic-resources.md b/documentation/topics/resources/polymorphic-resources.md index e7852795..9f6d11ac 100644 --- a/documentation/topics/resources/polymorphic-resources.md +++ b/documentation/topics/resources/polymorphic-resources.md @@ -1,3 +1,9 @@ + + # Polymorphic Resources To support leveraging the same resource backed by multiple tables (useful for things like polymorphic associations), AshPostgres supports setting the `data_layer.table` context for a given resource. For this example, lets assume that you have a `MyApp.Post` resource and a `MyApp.Comment` resource. For each of those resources, users can submit `reactions`. However, you want a separate table for `post_reactions` and `comment_reactions`. You could accomplish that like so: diff --git a/documentation/topics/resources/references.md b/documentation/topics/resources/references.md index 10ed6575..494c73b7 100644 --- a/documentation/topics/resources/references.md +++ b/documentation/topics/resources/references.md @@ -1,3 +1,9 @@ + + # References To configure the behavior of generated foreign keys on a resource, we use the `references` section, within the `postgres` configuration block. diff --git a/documentation/tutorials/get-started-with-ash-postgres.md b/documentation/tutorials/get-started-with-ash-postgres.md index 8e02c72a..f96c8659 100644 --- a/documentation/tutorials/get-started-with-ash-postgres.md +++ b/documentation/tutorials/get-started-with-ash-postgres.md @@ -1,3 +1,9 @@ + + # Get Started With Postgres ## Installation diff --git a/documentation/tutorials/set-up-with-existing-database.md b/documentation/tutorials/set-up-with-existing-database.md index 0c5d92c0..79b6a061 100644 --- a/documentation/tutorials/set-up-with-existing-database.md +++ b/documentation/tutorials/set-up-with-existing-database.md @@ -1,3 +1,9 @@ + + # Setting AshPostgres up with an existing database If you already have a postgres database and you'd like to get diff --git a/lib/ash_postgres.ex b/lib/ash_postgres.ex index d26103e4..34f5cd43 100644 --- a/lib/ash_postgres.ex +++ b/lib/ash_postgres.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres do @moduledoc """ The AshPostgres extension gives you tools to map a resource to a postgres database table. diff --git a/lib/check_constraint.ex b/lib/check_constraint.ex index a16e7346..6534a464 100644 --- a/lib/check_constraint.ex +++ b/lib/check_constraint.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CheckConstraint do @moduledoc "Represents a configured check constraint on the table backing a resource" diff --git a/lib/custom_aggregate.ex b/lib/custom_aggregate.ex index 9e581ce4..6a42b026 100644 --- a/lib/custom_aggregate.ex +++ b/lib/custom_aggregate.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CustomAggregate do @moduledoc """ A custom aggregate implementation for ecto. diff --git a/lib/custom_extension.ex b/lib/custom_extension.ex index 9360dc5c..968a748c 100644 --- a/lib/custom_extension.ex +++ b/lib/custom_extension.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CustomExtension do @moduledoc """ A custom extension implementation. diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 29496ceb..240b30d2 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CustomIndex do @moduledoc "Represents a custom index on the table backing a resource" @fields [ diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 6e3354d8..07812631 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DataLayer do require Ecto.Query require Ash.Expr diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 696439ae..933ca6e8 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DataLayer.Info do @moduledoc "Introspection functions for " diff --git a/lib/ecto_migration_default.ex b/lib/ecto_migration_default.ex index d0ba6dbb..d865471b 100644 --- a/lib/ecto_migration_default.ex +++ b/lib/ecto_migration_default.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defprotocol EctoMigrationDefault do @moduledoc """ Allows configuring how values are translated to default values in migrations. diff --git a/lib/extensions/immutable_raise_error.ex b/lib/extensions/immutable_raise_error.ex index b7ccf347..22760442 100644 --- a/lib/extensions/immutable_raise_error.ex +++ b/lib/extensions/immutable_raise_error.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Extensions.ImmutableRaiseError do @moduledoc """ An extension that installs an immutable version of ash_raise_error. diff --git a/lib/extensions/vector.ex b/lib/extensions/vector.ex index 3280e629..c029dcf4 100644 --- a/lib/extensions/vector.ex +++ b/lib/extensions/vector.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Extensions.Vector do @moduledoc """ An extension that adds support for the `vector` type. diff --git a/lib/functions/binding.ex b/lib/functions/binding.ex index 92fc9d6f..df20a209 100644 --- a/lib/functions/binding.ex +++ b/lib/functions/binding.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Functions.Binding do @moduledoc """ Refers to the current table binding. diff --git a/lib/functions/ilike.ex b/lib/functions/ilike.ex index 8fa403d4..dd960f5f 100644 --- a/lib/functions/ilike.ex +++ b/lib/functions/ilike.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Functions.ILike do @moduledoc """ Maps to the builtin postgres function `ilike`. diff --git a/lib/functions/like.ex b/lib/functions/like.ex index 8b2d51b0..fdc76c6b 100644 --- a/lib/functions/like.ex +++ b/lib/functions/like.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Functions.Like do @moduledoc """ Maps to the builtin postgres function `like`. diff --git a/lib/functions/trigram_similarity.ex b/lib/functions/trigram_similarity.ex index 97177ae6..19622647 100644 --- a/lib/functions/trigram_similarity.ex +++ b/lib/functions/trigram_similarity.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Functions.TrigramSimilarity do @moduledoc """ Maps to the builtin postgres trigram similarity function. Requires `pgtrgm` extension to be installed. diff --git a/lib/functions/vector_cosine_distance.ex b/lib/functions/vector_cosine_distance.ex index 349c1977..1a811583 100644 --- a/lib/functions/vector_cosine_distance.ex +++ b/lib/functions/vector_cosine_distance.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Functions.VectorCosineDistance do @moduledoc """ Maps to the vector cosine distance operator. Requires `vector` extension to be installed. diff --git a/lib/functions/vector_l2_distance.ex b/lib/functions/vector_l2_distance.ex index c3f13ca0..063a1432 100644 --- a/lib/functions/vector_l2_distance.ex +++ b/lib/functions/vector_l2_distance.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Functions.VectorL2Distance do @moduledoc """ Maps to the vector l2 distance operator. Requires `vector` extension to be installed. diff --git a/lib/igniter.ex b/lib/igniter.ex index 02ef4476..aa20746d 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + if Code.ensure_loaded?(Igniter) do defmodule AshPostgres.Igniter do @moduledoc "Codemods and utilities for working with AshPostgres & Igniter" diff --git a/lib/manual_relationship.ex b/lib/manual_relationship.ex index 081aee13..b6322de9 100644 --- a/lib/manual_relationship.ex +++ b/lib/manual_relationship.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ManualRelationship do @moduledoc "A behavior for postgres-specific manual relationship functionality" diff --git a/lib/migration.ex b/lib/migration.ex index 78e76f97..9102d2a9 100644 --- a/lib/migration.ex +++ b/lib/migration.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Migration do @moduledoc "Utilities for use in migrations" diff --git a/lib/migration_compile_cache.ex b/lib/migration_compile_cache.ex index 802c21ad..f421d3b6 100644 --- a/lib/migration_compile_cache.ex +++ b/lib/migration_compile_cache.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MigrationCompileCache do @moduledoc """ A cache for the compiled migrations. diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index a9c4659c..1206d829 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MigrationGenerator.AshFunctions do @latest_version 5 diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 00276e25..bdd82fba 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MigrationGenerator do @moduledoc false diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 88525c11..21af504b 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MigrationGenerator.Operation do @moduledoc false diff --git a/lib/migration_generator/phase.ex b/lib/migration_generator/phase.ex index a4e5861b..9b13dadd 100644 --- a/lib/migration_generator/phase.ex +++ b/lib/migration_generator/phase.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MigrationGenerator.Phase do @moduledoc false diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 316778fd..51db8a64 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Mix.Helpers do @moduledoc false def domains!(opts, args) do diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index a99baf33..61129c48 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.Create do use Mix.Task diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index e54ffddb..2c50e9b3 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.Drop do use Mix.Task diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index babac4f1..d207ea3f 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + if Code.ensure_loaded?(Igniter) do defmodule Mix.Tasks.AshPostgres.Gen.Resources do use Igniter.Mix.Task diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index 028ac8bb..ca16404d 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.GenerateMigrations do @moduledoc """ Generates migrations, and stores a snapshot of your resources. diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index 4935fdf1..e7b6e0fb 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + if Code.ensure_loaded?(Igniter) do defmodule Mix.Tasks.AshPostgres.Install do @moduledoc "Installs AshPostgres. Should be run with `mix igniter.install ash_postgres`" diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 7da14a78..601a5daf 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.Migrate do use Mix.Task diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index d59fc1bf..da11f74c 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.Rollback do use Mix.Task diff --git a/lib/mix/tasks/ash_postgres.setup_vector.ex b/lib/mix/tasks/ash_postgres.setup_vector.ex index c58b6f56..209c8241 100644 --- a/lib/mix/tasks/ash_postgres.setup_vector.ex +++ b/lib/mix/tasks/ash_postgres.setup_vector.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.SetupVector.Docs do @moduledoc false diff --git a/lib/mix/tasks/ash_postgres.squash_snapshots.ex b/lib/mix/tasks/ash_postgres.squash_snapshots.ex index 84032c9b..8e40a597 100644 --- a/lib/mix/tasks/ash_postgres.squash_snapshots.ex +++ b/lib/mix/tasks/ash_postgres.squash_snapshots.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.SquashSnapshots do use Mix.Task diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 6d70aa49..5660e963 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultiTenancy do @moduledoc false diff --git a/lib/reference.ex b/lib/reference.ex index e724a0b6..f22abc1f 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Reference do @moduledoc "Represents the configuration of a reference (i.e foreign key)." defstruct [ diff --git a/lib/repo.ex b/lib/repo.ex index e1b0726d..fdc2d522 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Repo do @moduledoc """ Resources that use `AshPostgres.DataLayer` use a `Repo` to access the database. diff --git a/lib/repo/before_compile.ex b/lib/repo/before_compile.ex index c2e9a310..c8306507 100644 --- a/lib/repo/before_compile.ex +++ b/lib/repo/before_compile.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Repo.BeforeCompile do @moduledoc false diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index b3a507b7..0f633f7b 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + if Code.ensure_loaded?(Igniter) do defmodule AshPostgres.ResourceGenerator do @moduledoc false diff --git a/lib/resource_generator/sensitive_data.ex b/lib/resource_generator/sensitive_data.ex index 7b720aef..30c94857 100644 --- a/lib/resource_generator/sensitive_data.ex +++ b/lib/resource_generator/sensitive_data.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ResourceGenerator.SensitiveData do @moduledoc false # I got this from ChatGPT, but this is a best effort transformation diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index 9997bbac..c4d8088e 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ResourceGenerator.Spec do @moduledoc false require Logger diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 71b7f03d..84d31935 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.SqlImplementation do @moduledoc false use AshSql.Implementation diff --git a/lib/statement.ex b/lib/statement.ex index dfc49322..5902d730 100644 --- a/lib/statement.ex +++ b/lib/statement.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Statement do @moduledoc "Represents a custom statement to be run in generated migrations" diff --git a/lib/type.ex b/lib/type.ex index 22a3ec07..88c6ef63 100644 --- a/lib/type.ex +++ b/lib/type.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Type do @moduledoc """ Postgres specific callbacks for `Ash.Type`. diff --git a/lib/types/ci_string_wrapper.ex b/lib/types/ci_string_wrapper.ex index 74e47347..5c7bb8cb 100644 --- a/lib/types/ci_string_wrapper.ex +++ b/lib/types/ci_string_wrapper.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Type.CiStringWrapper do @moduledoc false use Ash.Type.NewType, subtype_of: :ci_string, constraints: [allow_empty?: true, trim?: false] diff --git a/lib/types/ltree.ex b/lib/types/ltree.ex index 512758e2..0997bdff 100644 --- a/lib/types/ltree.ex +++ b/lib/types/ltree.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Ltree do @constraints [ escape?: [ diff --git a/lib/types/string_wrapper.ex b/lib/types/string_wrapper.ex index 84b5cd2c..5d8c33ea 100644 --- a/lib/types/string_wrapper.ex +++ b/lib/types/string_wrapper.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Type.StringWrapper do @moduledoc false use Ash.Type.NewType, subtype_of: :string, constraints: [allow_empty?: true, trim?: false] diff --git a/lib/types/timestamptz.ex b/lib/types/timestamptz.ex index 11b007a3..c684751c 100644 --- a/lib/types/timestamptz.ex +++ b/lib/types/timestamptz.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Timestamptz do @moduledoc """ Implements the PostgresSQL [timestamptz](https://www.postgresql.org/docs/current/datatype-datetime.html) (aka `timestamp with time zone`) type. diff --git a/lib/types/timestamptz_usec.ex b/lib/types/timestamptz_usec.ex index f1dae8fd..8bd0a38d 100644 --- a/lib/types/timestamptz_usec.ex +++ b/lib/types/timestamptz_usec.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TimestamptzUsec do @moduledoc """ Implements the PostgresSQL [timestamptz](https://www.postgresql.org/docs/current/datatype-datetime.html) (aka `timestamp with time zone`) type with nanosecond precision. diff --git a/lib/types/tsquery.ex b/lib/types/tsquery.ex index 9115b0e1..8e1fb7ad 100644 --- a/lib/types/tsquery.ex +++ b/lib/types/tsquery.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Tsquery do @moduledoc """ A thin wrapper around `:string` for working with tsquery types in calculations. diff --git a/lib/types/tsvector.ex b/lib/types/tsvector.ex index f753b2b1..1a29a359 100644 --- a/lib/types/tsvector.ex +++ b/lib/types/tsvector.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Tsvector do @moduledoc """ A type for representing postgres' tsvectors. diff --git a/lib/verifiers/ensure_table_or_polymorphic.ex b/lib/verifiers/ensure_table_or_polymorphic.ex index c2e612ca..9c1d17bc 100644 --- a/lib/verifiers/ensure_table_or_polymorphic.ex +++ b/lib/verifiers/ensure_table_or_polymorphic.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Verifiers.EnsureTableOrPolymorphic do @moduledoc false use Spark.Dsl.Verifier diff --git a/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex index 5e0a25cc..a595c95c 100644 --- a/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex +++ b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType do @moduledoc false use Spark.Dsl.Verifier diff --git a/lib/verifiers/prevent_multidimensional_array_aggregates.ex b/lib/verifiers/prevent_multidimensional_array_aggregates.ex index 82a38202..7589b277 100644 --- a/lib/verifiers/prevent_multidimensional_array_aggregates.ex +++ b/lib/verifiers/prevent_multidimensional_array_aggregates.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do @moduledoc false use Spark.Dsl.Verifier diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index 82457eb2..1c2fbad8 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Verifiers.ValidateIdentityIndexNames do @moduledoc false use Spark.Dsl.Verifier diff --git a/lib/verifiers/validate_references.ex b/lib/verifiers/validate_references.ex index 40c47a8e..efcffd2d 100644 --- a/lib/verifiers/validate_references.ex +++ b/lib/verifiers/validate_references.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Verifiers.ValidateReferences do @moduledoc false use Spark.Dsl.Verifier diff --git a/lib/version_agent.ex b/lib/version_agent.ex index e69de29b..0db6f4ff 100644 --- a/lib/version_agent.ex +++ b/lib/version_agent.ex @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT diff --git a/logos/small-logo.png.license b/logos/small-logo.png.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/logos/small-logo.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/mix.exs b/mix.exs index f1d82380..cf59bf71 100644 --- a/mix.exs +++ b/mix.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MixProject do use Mix.Project @@ -54,13 +58,20 @@ defmodule AshPostgres.MixProject do defp package do [ - name: :ash_postgres, + maintainers: [ + "Zach Daniel " + ], licenses: ["MIT"], files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG* documentation usage-rules.md), links: %{ - Changelog: "https://hexdocs.pm/ash_postgres/changelog.html", - GitHub: "https://github.com/ash-project/ash_postgres" + "GitHub" => "https://github.com/ash-project/ash_postgres", + "Changelog" => "https://github.com/ash-project/ash_postgres/blob/main/CHANGELOG.md", + "Discord" => "https://discord.gg/HTHRaaVPUc", + "Website" => "https://ash-hq.org/", + "Forum" => "https://elixirforum.com/c/elixir-framework-forums/ash-framework-forum", + "REUSE Compliance" => + "https://api.reuse.software/info/github.com/ash-project/ash_postgres" } ] end diff --git a/mix.lock.license b/mix.lock.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/mix.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs b/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs index 389b0b27..57fc3e50 100644 --- a/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs +++ b/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DevTestRepo.Migrations.MigrateResourcesExtensions1 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs b/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs index 3a56462c..bc0f2abf 100644 --- a/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs +++ b/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DevTestRepo.Migrations.MigrateResources1 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/resource_snapshots/dev_test_repo/extensions.json.license b/priv/resource_snapshots/dev_test_repo/extensions.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/dev_test_repo/extensions.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license b/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license b/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license b/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20220805191443.json.license b/priv/resource_snapshots/test_repo/authors/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20220914104733.json.license b/priv/resource_snapshots/test_repo/authors/20220914104733.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20220914104733.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20240327211150.json.license b/priv/resource_snapshots/test_repo/authors/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20240705113722.json.license b/priv/resource_snapshots/test_repo/authors/20240705113722.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20240705113722.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20250908212414.json.license b/priv/resource_snapshots/test_repo/authors/20250908212414.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20250908212414.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/chats/20250908093505.json.license b/priv/resource_snapshots/test_repo/chats/20250908093505.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/chats/20250908093505.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license b/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license b/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license b/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license b/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license b/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license b/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comments/20220805191443.json.license b/priv/resource_snapshots/test_repo/comments/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comments/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comments/20240327211150.json.license b/priv/resource_snapshots/test_repo/comments/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comments/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comments/20240327211917.json.license b/priv/resource_snapshots/test_repo/comments/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/comments/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license b/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license b/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license b/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license b/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/content/20250123164209.json.license b/priv/resource_snapshots/test_repo/content/20250123164209.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/content/20250123164209.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license b/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/csv/20250320225052.json.license b/priv/resource_snapshots/test_repo/csv/20250320225052.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/csv/20250320225052.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/customers/20250908073737.json.license b/priv/resource_snapshots/test_repo/customers/20250908073737.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/customers/20250908073737.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/entities/20240109160153.json.license b/priv/resource_snapshots/test_repo/entities/20240109160153.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/entities/20240109160153.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/entities/20240327211150.json.license b/priv/resource_snapshots/test_repo/entities/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/entities/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/entities/20240327211917.json.license b/priv/resource_snapshots/test_repo/entities/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/entities/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/extensions.json.license b/priv/resource_snapshots/test_repo/extensions.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/extensions.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license b/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/items/20240713134055.json.license b/priv/resource_snapshots/test_repo/items/20240713134055.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/items/20240713134055.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/items/20240717104854.json.license b/priv/resource_snapshots/test_repo/items/20240717104854.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/items/20240717104854.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/items/20240717153736.json.license b/priv/resource_snapshots/test_repo/items/20240717153736.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/items/20240717153736.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license b/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license b/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/managers/20230526144249.json.license b/priv/resource_snapshots/test_repo/managers/20230526144249.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/managers/20230526144249.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/managers/20240327211150.json.license b/priv/resource_snapshots/test_repo/managers/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/managers/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/messages/20250908093505.json.license b/priv/resource_snapshots/test_repo/messages/20250908093505.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/messages/20250908093505.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license b/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license b/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/note/20250123164209.json.license b/priv/resource_snapshots/test_repo/note/20250123164209.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/note/20250123164209.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orders/20250908073737.json.license b/priv/resource_snapshots/test_repo/orders/20250908073737.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orders/20250908073737.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license b/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license b/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license b/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license b/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license b/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/points/20250313112823.json.license b/priv/resource_snapshots/test_repo/points/20250313112823.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/points/20250313112823.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license b/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license b/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license b/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license b/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license b/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license b/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license b/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license b/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license b/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license b/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license b/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license b/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license b/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license b/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license b/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license b/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20220805191443.json.license b/priv/resource_snapshots/test_repo/posts/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20221125171150.json.license b/priv/resource_snapshots/test_repo/posts/20221125171150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20221125171150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20221125171204.json.license b/priv/resource_snapshots/test_repo/posts/20221125171204.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20221125171204.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20230129050950.json.license b/priv/resource_snapshots/test_repo/posts/20230129050950.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20230129050950.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20230823161017.json.license b/priv/resource_snapshots/test_repo/posts/20230823161017.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20230823161017.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20231127215636.json.license b/priv/resource_snapshots/test_repo/posts/20231127215636.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20231127215636.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20231129141453.json.license b/priv/resource_snapshots/test_repo/posts/20231129141453.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20231129141453.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20231219132807.json.license b/priv/resource_snapshots/test_repo/posts/20231219132807.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20231219132807.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240129221511.json.license b/priv/resource_snapshots/test_repo/posts/20240129221511.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240129221511.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240224001913.json.license b/priv/resource_snapshots/test_repo/posts/20240224001913.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240224001913.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240327211150.json.license b/priv/resource_snapshots/test_repo/posts/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240327211917.json.license b/priv/resource_snapshots/test_repo/posts/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240503012410.json.license b/priv/resource_snapshots/test_repo/posts/20240503012410.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240503012410.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240504185511.json.license b/priv/resource_snapshots/test_repo/posts/20240504185511.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240504185511.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240524031113.json.license b/priv/resource_snapshots/test_repo/posts/20240524031113.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240524031113.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240524041750.json.license b/priv/resource_snapshots/test_repo/posts/20240524041750.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240524041750.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240617193218.json.license b/priv/resource_snapshots/test_repo/posts/20240617193218.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240617193218.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240618102809.json.license b/priv/resource_snapshots/test_repo/posts/20240618102809.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240618102809.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240712232026.json.license b/priv/resource_snapshots/test_repo/posts/20240712232026.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240712232026.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240715135403.json.license b/priv/resource_snapshots/test_repo/posts/20240715135403.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240715135403.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240910180107.json.license b/priv/resource_snapshots/test_repo/posts/20240910180107.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240910180107.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240911225320.json.license b/priv/resource_snapshots/test_repo/posts/20240911225320.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240911225320.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240918104740.json.license b/priv/resource_snapshots/test_repo/posts/20240918104740.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240918104740.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240929121224.json.license b/priv/resource_snapshots/test_repo/posts/20240929121224.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20240929121224.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250217054207.json.license b/priv/resource_snapshots/test_repo/posts/20250217054207.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250217054207.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250313112823.json.license b/priv/resource_snapshots/test_repo/posts/20250313112823.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250313112823.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250520130634.json.license b/priv/resource_snapshots/test_repo/posts/20250520130634.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250520130634.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250521105654.json.license b/priv/resource_snapshots/test_repo/posts/20250521105654.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250521105654.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250612113920.json.license b/priv/resource_snapshots/test_repo/posts/20250612113920.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250612113920.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250618011917.json.license b/priv/resource_snapshots/test_repo/posts/20250618011917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/posts/20250618011917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/products/20250908073737.json.license b/priv/resource_snapshots/test_repo/products/20250908073737.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/products/20250908073737.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/profile/20220805191443.json.license b/priv/resource_snapshots/test_repo/profile/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/profile/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license b/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license b/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records/20240109160153.json.license b/priv/resource_snapshots/test_repo/records/20240109160153.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records/20240109160153.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records/20240327211150.json.license b/priv/resource_snapshots/test_repo/records/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records/20240327211917.json.license b/priv/resource_snapshots/test_repo/records/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license b/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license b/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license b/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license b/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license b/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license b/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license b/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license b/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license b/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license b/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tags/20250810102512.json.license b/priv/resource_snapshots/test_repo/tags/20250810102512.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tags/20250810102512.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license b/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license b/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license b/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license b/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20220805191443.json.license b/priv/resource_snapshots/test_repo/users/20220805191443.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20220805191443.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20221217123726.json.license b/priv/resource_snapshots/test_repo/users/20221217123726.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20221217123726.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20230129050950.json.license b/priv/resource_snapshots/test_repo/users/20230129050950.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20230129050950.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20240327211150.json.license b/priv/resource_snapshots/test_repo/users/20240327211150.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20240327211150.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20240727145758.json.license b/priv/resource_snapshots/test_repo/users/20240727145758.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20240727145758.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20240929124728.json.license b/priv/resource_snapshots/test_repo/users/20240929124728.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20240929124728.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20250320225052.json.license b/priv/resource_snapshots/test_repo/users/20250320225052.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20250320225052.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20250321142835.json.license b/priv/resource_snapshots/test_repo/users/20250321142835.json.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/resource_snapshots/test_repo/users/20250321142835.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/test_no_sandbox_repo/migrations/.gitkeep.license b/priv/test_no_sandbox_repo/migrations/.gitkeep.license new file mode 100644 index 00000000..815664f3 --- /dev/null +++ b/priv/test_no_sandbox_repo/migrations/.gitkeep.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Zach Daniel + +SPDX-License-Identifier: MIT diff --git a/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs b/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs index 09446bc3..94e450c6 100644 --- a/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs +++ b/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestNoSandboxRepo.Migrations.Install5Extensions20240627223222 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs b/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs index 97101dca..ecf0a75f 100644 --- a/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs +++ b/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestNoSandboxRepo.Migrations.InstallAshFunctionsExtension420240712232023 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs b/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs index 9794c3be..01697a60 100644 --- a/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs +++ b/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestNoSandboxRepo.Migrations.MigrateResourcesExtensions1 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20220805191440_install_4_extensions.exs b/priv/test_repo/migrations/20220805191440_install_4_extensions.exs index f301710b..a20aa8e9 100644 --- a/priv/test_repo/migrations/20220805191440_install_4_extensions.exs +++ b/priv/test_repo/migrations/20220805191440_install_4_extensions.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.Install4Extensions do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20220805191443_migrate_resources1.exs b/priv/test_repo/migrations/20220805191443_migrate_resources1.exs index 886d2de2..b9b7fdd0 100644 --- a/priv/test_repo/migrations/20220805191443_migrate_resources1.exs +++ b/priv/test_repo/migrations/20220805191443_migrate_resources1.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources1 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20220914104733_migrate_resources2.exs b/priv/test_repo/migrations/20220914104733_migrate_resources2.exs index 17ecb49f..fbbeb8e0 100644 --- a/priv/test_repo/migrations/20220914104733_migrate_resources2.exs +++ b/priv/test_repo/migrations/20220914104733_migrate_resources2.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources2 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20221017133955_migrate_resources3.exs b/priv/test_repo/migrations/20221017133955_migrate_resources3.exs index b30a8ba4..12f282d6 100644 --- a/priv/test_repo/migrations/20221017133955_migrate_resources3.exs +++ b/priv/test_repo/migrations/20221017133955_migrate_resources3.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources3 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20221125171148_migrate_resources4.exs b/priv/test_repo/migrations/20221125171148_migrate_resources4.exs index e58db10c..b6749230 100644 --- a/priv/test_repo/migrations/20221125171148_migrate_resources4.exs +++ b/priv/test_repo/migrations/20221125171148_migrate_resources4.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources4 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20221125171150_migrate_resources5.exs b/priv/test_repo/migrations/20221125171150_migrate_resources5.exs index e9268571..f4d14b56 100644 --- a/priv/test_repo/migrations/20221125171150_migrate_resources5.exs +++ b/priv/test_repo/migrations/20221125171150_migrate_resources5.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources5 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20221202194704_migrate_resources6.exs b/priv/test_repo/migrations/20221202194704_migrate_resources6.exs index 2d77266a..6b6f174a 100644 --- a/priv/test_repo/migrations/20221202194704_migrate_resources6.exs +++ b/priv/test_repo/migrations/20221202194704_migrate_resources6.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources6 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20221217123726_migrate_resources7.exs b/priv/test_repo/migrations/20221217123726_migrate_resources7.exs index 8ca2e4f2..95edab52 100644 --- a/priv/test_repo/migrations/20221217123726_migrate_resources7.exs +++ b/priv/test_repo/migrations/20221217123726_migrate_resources7.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources7 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20230129050950_migrate_resources8.exs b/priv/test_repo/migrations/20230129050950_migrate_resources8.exs index 4cfaa7da..3aba9340 100644 --- a/priv/test_repo/migrations/20230129050950_migrate_resources8.exs +++ b/priv/test_repo/migrations/20230129050950_migrate_resources8.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources8 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20230526144249_migrate_resources9.exs b/priv/test_repo/migrations/20230526144249_migrate_resources9.exs index a4008d52..d3a907b8 100644 --- a/priv/test_repo/migrations/20230526144249_migrate_resources9.exs +++ b/priv/test_repo/migrations/20230526144249_migrate_resources9.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources9 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs b/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs index 8c0b5fef..6f9ee9e4 100644 --- a/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs +++ b/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctions do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs b/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs index 5521466e..edfac408 100644 --- a/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs +++ b/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV0 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs b/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs index b82ee5c7..2222a781 100644 --- a/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs +++ b/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV1 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs b/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs index 19ded080..1cc444b7 100644 --- a/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs +++ b/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationTables do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20230823161017_migrate_resources10.exs b/priv/test_repo/migrations/20230823161017_migrate_resources10.exs index 314d7bea..18e6f479 100644 --- a/priv/test_repo/migrations/20230823161017_migrate_resources10.exs +++ b/priv/test_repo/migrations/20230823161017_migrate_resources10.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources10 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20230905050351_add_post_views.exs b/priv/test_repo/migrations/20230905050351_add_post_views.exs index b09ea4af..3ea0cbb1 100644 --- a/priv/test_repo/migrations/20230905050351_add_post_views.exs +++ b/priv/test_repo/migrations/20230905050351_add_post_views.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddPostViews do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs b/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs index e090e448..c3f768f0 100644 --- a/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs +++ b/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationsChannels do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20231127212608_add_composite_type.exs b/priv/test_repo/migrations/20231127212608_add_composite_type.exs index 9f1217ff..8a85a46f 100644 --- a/priv/test_repo/migrations/20231127212608_add_composite_type.exs +++ b/priv/test_repo/migrations/20231127212608_add_composite_type.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddCompositeType do use Ecto.Migration diff --git a/priv/test_repo/migrations/20231127215636_migrate_resources11.exs b/priv/test_repo/migrations/20231127215636_migrate_resources11.exs index 06eb1c5b..c4da8e77 100644 --- a/priv/test_repo/migrations/20231127215636_migrate_resources11.exs +++ b/priv/test_repo/migrations/20231127215636_migrate_resources11.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources11 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20231129141453_migrate_resources12.exs b/priv/test_repo/migrations/20231129141453_migrate_resources12.exs index 1fbcbd28..613a6b42 100644 --- a/priv/test_repo/migrations/20231129141453_migrate_resources12.exs +++ b/priv/test_repo/migrations/20231129141453_migrate_resources12.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources12 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs index d3192fdc..e02f5467 100644 --- a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs +++ b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension2 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20231219132807_migrate_resources13.exs b/priv/test_repo/migrations/20231219132807_migrate_resources13.exs index 088d2ae3..55fc4bbe 100644 --- a/priv/test_repo/migrations/20231219132807_migrate_resources13.exs +++ b/priv/test_repo/migrations/20231219132807_migrate_resources13.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources13 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs b/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs index 2417e5d7..e841dfba 100644 --- a/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs +++ b/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension3 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20240109155951_create_temp_schema.exs b/priv/test_repo/migrations/20240109155951_create_temp_schema.exs index 2ecb9500..ada02c90 100644 --- a/priv/test_repo/migrations/20240109155951_create_temp_schema.exs +++ b/priv/test_repo/migrations/20240109155951_create_temp_schema.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.CreateTempSchema do use Ecto.Migration diff --git a/priv/test_repo/migrations/20240109160153_migrate_resources14.exs b/priv/test_repo/migrations/20240109160153_migrate_resources14.exs index 22bec37b..c70f9357 100644 --- a/priv/test_repo/migrations/20240109160153_migrate_resources14.exs +++ b/priv/test_repo/migrations/20240109160153_migrate_resources14.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources14 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240129221511_migrate_resources15.exs b/priv/test_repo/migrations/20240129221511_migrate_resources15.exs index 3ee35d6b..88ae8d08 100644 --- a/priv/test_repo/migrations/20240129221511_migrate_resources15.exs +++ b/priv/test_repo/migrations/20240129221511_migrate_resources15.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources15 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs b/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs index f54f17a0..36ad998e 100644 --- a/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs +++ b/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddResourcesForSubqueryTest do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240224001913_migrate_resources16.exs b/priv/test_repo/migrations/20240224001913_migrate_resources16.exs index a9b687e1..00decc17 100644 --- a/priv/test_repo/migrations/20240224001913_migrate_resources16.exs +++ b/priv/test_repo/migrations/20240224001913_migrate_resources16.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources16 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240227180858_migrate_resources17.exs b/priv/test_repo/migrations/20240227180858_migrate_resources17.exs index 1778a5e2..93fc8bd5 100644 --- a/priv/test_repo/migrations/20240227180858_migrate_resources17.exs +++ b/priv/test_repo/migrations/20240227180858_migrate_resources17.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources17 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240227181137_migrate_resources18.exs b/priv/test_repo/migrations/20240227181137_migrate_resources18.exs index f55f7b3c..4e6db225 100644 --- a/priv/test_repo/migrations/20240227181137_migrate_resources18.exs +++ b/priv/test_repo/migrations/20240227181137_migrate_resources18.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources18 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs index c3708b31..bdecc817 100644 --- a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs +++ b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.Install5Extensions do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20240327211150_migrate_resources19.exs b/priv/test_repo/migrations/20240327211150_migrate_resources19.exs index 787d0e26..7fc2c8c3 100644 --- a/priv/test_repo/migrations/20240327211150_migrate_resources19.exs +++ b/priv/test_repo/migrations/20240327211150_migrate_resources19.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources19 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240327211917_migrate_resources20.exs b/priv/test_repo/migrations/20240327211917_migrate_resources20.exs index 030206f5..bd30cc5b 100644 --- a/priv/test_repo/migrations/20240327211917_migrate_resources20.exs +++ b/priv/test_repo/migrations/20240327211917_migrate_resources20.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources20 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240503012410_migrate_resources21.exs b/priv/test_repo/migrations/20240503012410_migrate_resources21.exs index 70781d33..9230a78a 100644 --- a/priv/test_repo/migrations/20240503012410_migrate_resources21.exs +++ b/priv/test_repo/migrations/20240503012410_migrate_resources21.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources21 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240504185511_migrate_resources22.exs b/priv/test_repo/migrations/20240504185511_migrate_resources22.exs index 86257d61..19ae6c87 100644 --- a/priv/test_repo/migrations/20240504185511_migrate_resources22.exs +++ b/priv/test_repo/migrations/20240504185511_migrate_resources22.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources22 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240516205244_migrate_resources23.exs b/priv/test_repo/migrations/20240516205244_migrate_resources23.exs index 1d0eec51..cebacd08 100644 --- a/priv/test_repo/migrations/20240516205244_migrate_resources23.exs +++ b/priv/test_repo/migrations/20240516205244_migrate_resources23.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources23 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240517223946_migrate_resources24.exs b/priv/test_repo/migrations/20240517223946_migrate_resources24.exs index 22998699..09e71bd5 100644 --- a/priv/test_repo/migrations/20240517223946_migrate_resources24.exs +++ b/priv/test_repo/migrations/20240517223946_migrate_resources24.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources24 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240524031113_migrate_resources25.exs b/priv/test_repo/migrations/20240524031113_migrate_resources25.exs index 9c524d71..0b829659 100644 --- a/priv/test_repo/migrations/20240524031113_migrate_resources25.exs +++ b/priv/test_repo/migrations/20240524031113_migrate_resources25.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources25 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240524041750_migrate_resources26.exs b/priv/test_repo/migrations/20240524041750_migrate_resources26.exs index a65fb877..e27aff26 100644 --- a/priv/test_repo/migrations/20240524041750_migrate_resources26.exs +++ b/priv/test_repo/migrations/20240524041750_migrate_resources26.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources26 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240610195853_migrate_resources27.exs b/priv/test_repo/migrations/20240610195853_migrate_resources27.exs index c31f8819..f156231e 100644 --- a/priv/test_repo/migrations/20240610195853_migrate_resources27.exs +++ b/priv/test_repo/migrations/20240610195853_migrate_resources27.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources27 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240617193218_migrate_resources28.exs b/priv/test_repo/migrations/20240617193218_migrate_resources28.exs index d112c67a..d864e25b 100644 --- a/priv/test_repo/migrations/20240617193218_migrate_resources28.exs +++ b/priv/test_repo/migrations/20240617193218_migrate_resources28.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources28 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240618085942_migrate_resources29.exs b/priv/test_repo/migrations/20240618085942_migrate_resources29.exs index f8e6fe26..0c6b8de3 100644 --- a/priv/test_repo/migrations/20240618085942_migrate_resources29.exs +++ b/priv/test_repo/migrations/20240618085942_migrate_resources29.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources29 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240618102809_migrate_resources30.exs b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs index f3cec44b..4d1f8ad8 100644 --- a/priv/test_repo/migrations/20240618102809_migrate_resources30.exs +++ b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources30 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs b/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs index 17c00fcc..3a544490 100644 --- a/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs +++ b/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension420240622192713 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20240627223225_migrate_resources31.exs b/priv/test_repo/migrations/20240627223225_migrate_resources31.exs index 8a90e3d4..cd97c368 100644 --- a/priv/test_repo/migrations/20240627223225_migrate_resources31.exs +++ b/priv/test_repo/migrations/20240627223225_migrate_resources31.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources31 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240703155134_migrate_resources32.exs b/priv/test_repo/migrations/20240703155134_migrate_resources32.exs index 47f472a2..1364a828 100644 --- a/priv/test_repo/migrations/20240703155134_migrate_resources32.exs +++ b/priv/test_repo/migrations/20240703155134_migrate_resources32.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources32 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240705113722_migrate_resources33.exs b/priv/test_repo/migrations/20240705113722_migrate_resources33.exs index e06ec7c7..e6f7a1a8 100644 --- a/priv/test_repo/migrations/20240705113722_migrate_resources33.exs +++ b/priv/test_repo/migrations/20240705113722_migrate_resources33.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources33 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240712232026_migrate_resources34.exs b/priv/test_repo/migrations/20240712232026_migrate_resources34.exs index af406cf3..22af731f 100644 --- a/priv/test_repo/migrations/20240712232026_migrate_resources34.exs +++ b/priv/test_repo/migrations/20240712232026_migrate_resources34.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources34 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs b/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs index 04dce9a0..d6c57db6 100644 --- a/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs +++ b/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MultiDomainCalculations do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240715135403_migrate_resources35.exs b/priv/test_repo/migrations/20240715135403_migrate_resources35.exs index 5819ea53..2349c59d 100644 --- a/priv/test_repo/migrations/20240715135403_migrate_resources35.exs +++ b/priv/test_repo/migrations/20240715135403_migrate_resources35.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources35 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs b/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs index 04a9f3f6..2c9269fa 100644 --- a/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs +++ b/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.NoAttributesCalculationTest do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240717151815_migrate_resources36.exs b/priv/test_repo/migrations/20240717151815_migrate_resources36.exs index 1a885fcf..e8b21183 100644 --- a/priv/test_repo/migrations/20240717151815_migrate_resources36.exs +++ b/priv/test_repo/migrations/20240717151815_migrate_resources36.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources36 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240717153736_migrate_resources37.exs b/priv/test_repo/migrations/20240717153736_migrate_resources37.exs index ba2f6bfe..4d5777a4 100644 --- a/priv/test_repo/migrations/20240717153736_migrate_resources37.exs +++ b/priv/test_repo/migrations/20240717153736_migrate_resources37.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources37 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240727145758_user_invites.exs b/priv/test_repo/migrations/20240727145758_user_invites.exs index abfce2fb..c054a440 100644 --- a/priv/test_repo/migrations/20240727145758_user_invites.exs +++ b/priv/test_repo/migrations/20240727145758_user_invites.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.UserInvites do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240906170759_migrate_resources38.exs b/priv/test_repo/migrations/20240906170759_migrate_resources38.exs index f1211140..78637f88 100644 --- a/priv/test_repo/migrations/20240906170759_migrate_resources38.exs +++ b/priv/test_repo/migrations/20240906170759_migrate_resources38.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources38 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240910180107_migrate_resources39.exs b/priv/test_repo/migrations/20240910180107_migrate_resources39.exs index adf27660..dc7f6502 100644 --- a/priv/test_repo/migrations/20240910180107_migrate_resources39.exs +++ b/priv/test_repo/migrations/20240910180107_migrate_resources39.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources39 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240911225319_install_1_extensions.exs b/priv/test_repo/migrations/20240911225319_install_1_extensions.exs index 0f2eb12b..dede4e89 100644 --- a/priv/test_repo/migrations/20240911225319_install_1_extensions.exs +++ b/priv/test_repo/migrations/20240911225319_install_1_extensions.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.Install1Extensions20240911225317 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20240911225320_migrate_resources40.exs b/priv/test_repo/migrations/20240911225320_migrate_resources40.exs index 1e1cd754..fd10a2f0 100644 --- a/priv/test_repo/migrations/20240911225320_migrate_resources40.exs +++ b/priv/test_repo/migrations/20240911225320_migrate_resources40.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources40 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240918104740_migrate_resources41.exs b/priv/test_repo/migrations/20240918104740_migrate_resources41.exs index 118fe8fc..41ce709a 100644 --- a/priv/test_repo/migrations/20240918104740_migrate_resources41.exs +++ b/priv/test_repo/migrations/20240918104740_migrate_resources41.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources41 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240929121224_migrate_resources42.exs b/priv/test_repo/migrations/20240929121224_migrate_resources42.exs index 529a2338..23a14e4a 100644 --- a/priv/test_repo/migrations/20240929121224_migrate_resources42.exs +++ b/priv/test_repo/migrations/20240929121224_migrate_resources42.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources42 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20240929124728_migrate_resources43.exs b/priv/test_repo/migrations/20240929124728_migrate_resources43.exs index 31e815f9..c91d401a 100644 --- a/priv/test_repo/migrations/20240929124728_migrate_resources43.exs +++ b/priv/test_repo/migrations/20240929124728_migrate_resources43.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources43 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20241208221219_migrate_resources44.exs b/priv/test_repo/migrations/20241208221219_migrate_resources44.exs index 3665d579..af546799 100644 --- a/priv/test_repo/migrations/20241208221219_migrate_resources44.exs +++ b/priv/test_repo/migrations/20241208221219_migrate_resources44.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources44 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20241217232254_migrate_resources45.exs b/priv/test_repo/migrations/20241217232254_migrate_resources45.exs index acfc2b39..94454ef2 100644 --- a/priv/test_repo/migrations/20241217232254_migrate_resources45.exs +++ b/priv/test_repo/migrations/20241217232254_migrate_resources45.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources45 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs b/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs index e6efdcb9..68c52ef7 100644 --- a/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs +++ b/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResourcesExtensions1 do @moduledoc """ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback diff --git a/priv/test_repo/migrations/20250122190558_migrate_resources46.exs b/priv/test_repo/migrations/20250122190558_migrate_resources46.exs index 9f7eec04..c9cfed58 100644 --- a/priv/test_repo/migrations/20250122190558_migrate_resources46.exs +++ b/priv/test_repo/migrations/20250122190558_migrate_resources46.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources46 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250123161002_migrate_resources47.exs b/priv/test_repo/migrations/20250123161002_migrate_resources47.exs index 59036b61..32e5ff54 100644 --- a/priv/test_repo/migrations/20250123161002_migrate_resources47.exs +++ b/priv/test_repo/migrations/20250123161002_migrate_resources47.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources47 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250123164209_migrate_resources48.exs b/priv/test_repo/migrations/20250123164209_migrate_resources48.exs index 99bc4982..b07394fb 100644 --- a/priv/test_repo/migrations/20250123164209_migrate_resources48.exs +++ b/priv/test_repo/migrations/20250123164209_migrate_resources48.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources48 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250210191116_migrate_resources49.exs b/priv/test_repo/migrations/20250210191116_migrate_resources49.exs index 622dddc6..f147420b 100644 --- a/priv/test_repo/migrations/20250210191116_migrate_resources49.exs +++ b/priv/test_repo/migrations/20250210191116_migrate_resources49.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources49 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250217054207_migrate_resources50.exs b/priv/test_repo/migrations/20250217054207_migrate_resources50.exs index 58a69c80..1ea35a4a 100644 --- a/priv/test_repo/migrations/20250217054207_migrate_resources50.exs +++ b/priv/test_repo/migrations/20250217054207_migrate_resources50.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources50 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250313112823_migrate_resources51.exs b/priv/test_repo/migrations/20250313112823_migrate_resources51.exs index fe51caf5..37609641 100644 --- a/priv/test_repo/migrations/20250313112823_migrate_resources51.exs +++ b/priv/test_repo/migrations/20250313112823_migrate_resources51.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources51 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250320225052_add_csv_resource.exs b/priv/test_repo/migrations/20250320225052_add_csv_resource.exs index b02499d1..11159d77 100644 --- a/priv/test_repo/migrations/20250320225052_add_csv_resource.exs +++ b/priv/test_repo/migrations/20250320225052_add_csv_resource.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddCsvResource do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250321142835_migrate_resources52.exs b/priv/test_repo/migrations/20250321142835_migrate_resources52.exs index a347e279..68ee928c 100644 --- a/priv/test_repo/migrations/20250321142835_migrate_resources52.exs +++ b/priv/test_repo/migrations/20250321142835_migrate_resources52.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources52 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs b/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs index 1e565f40..ff4a368c 100644 --- a/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs +++ b/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddPunchlinesAndStandupClubs do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250519103535_migrate_resources53.exs b/priv/test_repo/migrations/20250519103535_migrate_resources53.exs index 6059ae3e..cabe0ee7 100644 --- a/priv/test_repo/migrations/20250519103535_migrate_resources53.exs +++ b/priv/test_repo/migrations/20250519103535_migrate_resources53.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources53 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250520130634_migrate_resources54.exs b/priv/test_repo/migrations/20250520130634_migrate_resources54.exs index 9d8a51cb..063d79b9 100644 --- a/priv/test_repo/migrations/20250520130634_migrate_resources54.exs +++ b/priv/test_repo/migrations/20250520130634_migrate_resources54.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources54 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs b/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs index bde744b0..734de6d5 100644 --- a/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs +++ b/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddModelTupleToPost do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs b/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs index fe910c8c..90c3fcba 100644 --- a/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs +++ b/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.CreateRecordTempEntitiesTable do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250612113920_migrate_resources55.exs b/priv/test_repo/migrations/20250612113920_migrate_resources55.exs index 082dad95..b91c4fa9 100644 --- a/priv/test_repo/migrations/20250612113920_migrate_resources55.exs +++ b/priv/test_repo/migrations/20250612113920_migrate_resources55.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources55 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250618011917_migrate_resources56.exs b/priv/test_repo/migrations/20250618011917_migrate_resources56.exs index 641aa008..97d68971 100644 --- a/priv/test_repo/migrations/20250618011917_migrate_resources56.exs +++ b/priv/test_repo/migrations/20250618011917_migrate_resources56.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources56 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs b/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs index 88d73d15..fdcd91e4 100644 --- a/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs +++ b/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationsFolderAndItems do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250731124648_migrate_resources57.exs b/priv/test_repo/migrations/20250731124648_migrate_resources57.exs index 3493fd6b..3fea4408 100644 --- a/priv/test_repo/migrations/20250731124648_migrate_resources57.exs +++ b/priv/test_repo/migrations/20250731124648_migrate_resources57.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources57 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250810102512_migrate_resources58.exs b/priv/test_repo/migrations/20250810102512_migrate_resources58.exs index 484e4dc0..de4bbd5a 100644 --- a/priv/test_repo/migrations/20250810102512_migrate_resources58.exs +++ b/priv/test_repo/migrations/20250810102512_migrate_resources58.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources58 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250908073737_migrate_resources59.exs b/priv/test_repo/migrations/20250908073737_migrate_resources59.exs index a4acd79a..dee8b387 100644 --- a/priv/test_repo/migrations/20250908073737_migrate_resources59.exs +++ b/priv/test_repo/migrations/20250908073737_migrate_resources59.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources59 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250908093505_migrate_resources60.exs b/priv/test_repo/migrations/20250908093505_migrate_resources60.exs index 404906e5..d3747224 100644 --- a/priv/test_repo/migrations/20250908093505_migrate_resources60.exs +++ b/priv/test_repo/migrations/20250908093505_migrate_resources60.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources60 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20250908212414_migrate_resources61.exs b/priv/test_repo/migrations/20250908212414_migrate_resources61.exs index 303a4e53..2bd3d6bf 100644 --- a/priv/test_repo/migrations/20250908212414_migrate_resources61.exs +++ b/priv/test_repo/migrations/20250908212414_migrate_resources61.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources61 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/migrations/20251002180954_migrate_resources62.exs b/priv/test_repo/migrations/20251002180954_migrate_resources62.exs index fe90b42c..8fd9c180 100644 --- a/priv/test_repo/migrations/20251002180954_migrate_resources62.exs +++ b/priv/test_repo/migrations/20251002180954_migrate_resources62.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.Migrations.MigrateResources62 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs b/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs index 0ce1caf3..5a5bd69b 100644 --- a/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs +++ b/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources1 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs b/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs index 0e4da87a..56751e6c 100644 --- a/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs +++ b/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources2 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs b/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs index dc378342..8c913034 100644 --- a/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs +++ b/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources3 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs b/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs index 36cbc1d1..ab537413 100644 --- a/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs +++ b/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources4 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs b/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs index 923ba511..2a52a954 100644 --- a/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs +++ b/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources5 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs b/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs index f5018509..18a7fdfd 100644 --- a/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs +++ b/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources6 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs index 42a6f5a3..6170f2f2 100644 --- a/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs +++ b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources7 do @moduledoc """ Updates resources based on their most recent snapshots. diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 4803ec5c..ff51bf26 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshSql.AggregateTest do use AshPostgres.RepoCase, async: false import ExUnit.CaptureIO diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index a3318b57..41c46e31 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgresTest do use AshPostgres.RepoCase, async: false import ExUnit.CaptureLog diff --git a/test/atomics_test.exs b/test/atomics_test.exs index 2439ab9d..b9443acf 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.AtomicsTest do alias AshPostgres.Test.Author alias AshPostgres.Test.Comment diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index d6fd8c17..e48d0238 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.BulkCreateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Post, Record} diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index ff159a8f..24cf2395 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.BulkDestroyTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Permalink diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 092f964d..38ce2f45 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.BulkUpdateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{CSV, Post, Record} diff --git a/test/calculation_test.exs b/test/calculation_test.exs index cf539f18..38e4cf5b 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CalculationTest do alias AshPostgres.Test.RecordTempEntity use AshPostgres.RepoCase, async: false diff --git a/test/cascade_destroy_test.exs b/test/cascade_destroy_test.exs index a407ea35..062e0378 100644 --- a/test/cascade_destroy_test.exs +++ b/test/cascade_destroy_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgresTest.CascadeDestroyTest do use AshPostgres.RepoCase, async: true diff --git a/test/combination_test.exs b/test/combination_test.exs index 5faaa020..fdb55d35 100644 --- a/test/combination_test.exs +++ b/test/combination_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CombinationTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 5617a6d1..815071f7 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculationsTest do use AshPostgres.RepoCase, async: false diff --git a/test/composite_type_test.exs b/test/composite_type_test.exs index b1a3990c..7e73e198 100644 --- a/test/composite_type_test.exs +++ b/test/composite_type_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CompositeTypeTest do use AshPostgres.RepoCase alias AshPostgres.Test.Post diff --git a/test/constraint_test.exs b/test/constraint_test.exs index b2ec9fb8..7c63f3c5 100644 --- a/test/constraint_test.exs +++ b/test/constraint_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ConstraintTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/create_test.exs b/test/create_test.exs index 84607880..92effd16 100644 --- a/test/create_test.exs +++ b/test/create_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.CreateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/custom_expression_test.exs b/test/custom_expression_test.exs index e88737ad..7f204eef 100644 --- a/test/custom_expression_test.exs +++ b/test/custom_expression_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CustomExpressionTest do use AshPostgres.RepoCase, async: false diff --git a/test/custom_index_test.exs b/test/custom_index_test.exs index 1b021813..9d02b655 100644 --- a/test/custom_index_test.exs +++ b/test/custom_index_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CustomIndexTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs index 8b204f25..02d702f5 100644 --- a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs +++ b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.EmptyAtomicNonBulkActionsPolicyBypassTest do @moduledoc """ This is test verifies the fix for the following CVE: diff --git a/test/destroy_test.exs b/test/destroy_test.exs index 974f2cb3..208b2038 100644 --- a/test/destroy_test.exs +++ b/test/destroy_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DestroyTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/dev_migrations_test.exs b/test/dev_migrations_test.exs index fdaeeff9..64cbb7c1 100644 --- a/test/dev_migrations_test.exs +++ b/test/dev_migrations_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DevMigrationsTest do use AshPostgres.RepoCase, async: false @moduletag :migration diff --git a/test/distinct_test.exs b/test/distinct_test.exs index 284ed5b5..a616cac8 100644 --- a/test/distinct_test.exs +++ b/test/distinct_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DistinctTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/ecto_compatibility_test.exs b/test/ecto_compatibility_test.exs index 3b3555ec..3670d021 100644 --- a/test/ecto_compatibility_test.exs +++ b/test/ecto_compatibility_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.EctoCompatibilityTest do use AshPostgres.RepoCase, async: false require Ash.Query diff --git a/test/embeddable_resource_test.exs b/test/embeddable_resource_test.exs index 4edb0903..d30c11c3 100644 --- a/test/embeddable_resource_test.exs +++ b/test/embeddable_resource_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.EmbeddableResourceTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/enum_test.exs b/test/enum_test.exs index 537944c4..0dea90a8 100644 --- a/test/enum_test.exs +++ b/test/enum_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.EnumTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/error_expr_test.exs b/test/error_expr_test.exs index d73577ad..98719104 100644 --- a/test/error_expr_test.exs +++ b/test/error_expr_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ErrorExprTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/filter_child_relationship_by_parent_relationship_test.exs b/test/filter_child_relationship_by_parent_relationship_test.exs index fb2f4e52..63a50eef 100644 --- a/test/filter_child_relationship_by_parent_relationship_test.exs +++ b/test/filter_child_relationship_by_parent_relationship_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Support.Relationships.FilterChileRelationshipByParentRelationshipTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Comment, Post} diff --git a/test/filter_field_policy_test.exs b/test/filter_field_policy_test.exs index a69f2429..6778e70b 100644 --- a/test/filter_field_policy_test.exs +++ b/test/filter_field_policy_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule FilterFieldPolicyTest do use AshPostgres.RepoCase, async: false diff --git a/test/filter_test.exs b/test/filter_test.exs index d0332301..0172fb81 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.FilterTest do use AshPostgres.RepoCase, async: false diff --git a/test/load_test.exs b/test/load_test.exs index 63b142cd..fa9c8aba 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false diff --git a/test/lock_test.exs b/test/lock_test.exs index f36c8ed2..01122f50 100644 --- a/test/lock_test.exs +++ b/test/lock_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.LockTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/ltree_test.exs b/test/ltree_test.exs index cbd54ab1..33b3c1ba 100644 --- a/test/ltree_test.exs +++ b/test/ltree_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.LtreeTest do use AshPostgres.RepoCase, async: true use ExUnitProperties diff --git a/test/manual_relationships_test.exs b/test/manual_relationships_test.exs index c1921dde..3f4af170 100644 --- a/test/manual_relationships_test.exs +++ b/test/manual_relationships_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ManualRelationshipsTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Comment, Post} diff --git a/test/manual_update_test.exs b/test/manual_update_test.exs index e5a286cf..74a6ec35 100644 --- a/test/manual_update_test.exs +++ b/test/manual_update_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ManualUpdateTest do use AshPostgres.RepoCase, async: true diff --git a/test/many_to_many_expr_test.exs b/test/many_to_many_expr_test.exs index a19fe509..942168fc 100644 --- a/test/many_to_many_expr_test.exs +++ b/test/many_to_many_expr_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ManyToManyExprTest do use AshPostgres.RepoCase, async: false diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index a12a09a0..41b16774 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MigrationGeneratorTest do use AshPostgres.RepoCase, async: false @moduletag :migration diff --git a/test/mix/tasks/ash_postgres.install_test.exs b/test/mix/tasks/ash_postgres.install_test.exs index e90b69ee..e8ac1154 100644 --- a/test/mix/tasks/ash_postgres.install_test.exs +++ b/test/mix/tasks/ash_postgres.install_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Mix.Tasks.AshPostgres.InstallTest do use ExUnit.Case diff --git a/test/mix_squash_snapshots_test.exs b/test/mix_squash_snapshots_test.exs index dcdce819..352430ae 100644 --- a/test/mix_squash_snapshots_test.exs +++ b/test/mix_squash_snapshots_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MixSquashSnapshotsTest do use AshPostgres.RepoCase, async: false @moduletag :migration diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs index 8cc9bc55..54652f63 100644 --- a/test/multi_domain_calculations_test.exs +++ b/test/multi_domain_calculations_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculationsTest do use AshPostgres.RepoCase, async: false diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 85a97cbe..3e17c1ad 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultitenancyTest do use AshPostgres.RepoCase, async: false diff --git a/test/parent_filter_test.exs b/test/parent_filter_test.exs index b7f8f8dc..42f8326e 100644 --- a/test/parent_filter_test.exs +++ b/test/parent_filter_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ParentFilterTest do use AshPostgres.RepoCase, async: false diff --git a/test/parent_sort_test.exs b/test/parent_sort_test.exs index 423d6634..7d472f2d 100644 --- a/test/parent_sort_test.exs +++ b/test/parent_sort_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ParentSortTest do use AshPostgres.RepoCase, async: false diff --git a/test/polymorphism_test.exs b/test/polymorphism_test.exs index 4612a3c8..ecd8dc9d 100644 --- a/test/polymorphism_test.exs +++ b/test/polymorphism_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.PolymorphismTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.{Post, Rating} diff --git a/test/primary_key_test.exs b/test/primary_key_test.exs index af12e799..8812bc0e 100644 --- a/test/primary_key_test.exs +++ b/test/primary_key_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PrimaryKeyTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/references_test.exs b/test/references_test.exs index 7632c9f8..59151ba3 100644 --- a/test/references_test.exs +++ b/test/references_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ReferencesTest do use AshPostgres.RepoCase import ExUnit.CaptureIO diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs index d8fa5161..bdcc301d 100644 --- a/test/rel_with_parent_filter_test.exs +++ b/test/rel_with_parent_filter_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.RelWithParentFilterTest do use AshPostgres.RepoCase, async: false diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index 4878cf6d..4c1f916c 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.ResourceGeenratorTests do use AshPostgres.RepoCase, async: false diff --git a/test/schema_test.exs b/test/schema_test.exs index c8be1cf8..be04d3bb 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.SchemaTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/select_test.exs b/test/select_test.exs index 219fdc77..7c440b10 100644 --- a/test/select_test.exs +++ b/test/select_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.SelectTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/sort_test.exs b/test/sort_test.exs index e7d949da..c1afc99a 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.SortTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs index ca433860..efdb0195 100644 --- a/test/storage_types_test.exs +++ b/test/storage_types_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.StorageTypesTest do use AshPostgres.RepoCase, async: false diff --git a/test/subquery_test.exs b/test/subquery_test.exs index 1a9e69dd..9795730b 100644 --- a/test/subquery_test.exs +++ b/test/subquery_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.SubqueryTest do use AshPostgres.RepoCase, async: false diff --git a/test/support/complex_calculations/domain.ex b/test/support/complex_calculations/domain.ex index 15a306ef..6356daa3 100644 --- a/test/support/complex_calculations/domain.ex +++ b/test/support/complex_calculations/domain.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.Domain do @moduledoc false use Ash.Domain diff --git a/test/support/complex_calculations/resources/certification.ex b/test/support/complex_calculations/resources/certification.ex index 6310d1c7..317a5dc1 100644 --- a/test/support/complex_calculations/resources/certification.ex +++ b/test/support/complex_calculations/resources/certification.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.Certification do @moduledoc false use Ash.Resource, diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex index eed91879..af99ed29 100644 --- a/test/support/complex_calculations/resources/channel.ex +++ b/test/support/complex_calculations/resources/channel.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.Channel do @moduledoc false use Ash.Resource, diff --git a/test/support/complex_calculations/resources/channel_member.ex b/test/support/complex_calculations/resources/channel_member.ex index c9d99997..7feb3c9d 100644 --- a/test/support/complex_calculations/resources/channel_member.ex +++ b/test/support/complex_calculations/resources/channel_member.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do @moduledoc false use Ash.Resource, diff --git a/test/support/complex_calculations/resources/dm_channel.ex b/test/support/complex_calculations/resources/dm_channel.ex index ed3a6397..2113a3d5 100644 --- a/test/support/complex_calculations/resources/dm_channel.ex +++ b/test/support/complex_calculations/resources/dm_channel.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.DMChannel do @moduledoc false use Ash.Resource, diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex index 405c9dae..88a3b7df 100644 --- a/test/support/complex_calculations/resources/documentation.ex +++ b/test/support/complex_calculations/resources/documentation.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.Documentation do @moduledoc false use Ash.Resource, diff --git a/test/support/complex_calculations/resources/folder.ex b/test/support/complex_calculations/resources/folder.ex index d4b31e38..c0faefe6 100644 --- a/test/support/complex_calculations/resources/folder.ex +++ b/test/support/complex_calculations/resources/folder.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Support.ComplexCalculations.Folder do @moduledoc """ A tree structure using the ltree type. diff --git a/test/support/complex_calculations/resources/folder_item.ex b/test/support/complex_calculations/resources/folder_item.ex index ac115aef..7404970e 100644 --- a/test/support/complex_calculations/resources/folder_item.ex +++ b/test/support/complex_calculations/resources/folder_item.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Support.ComplexCalculations.FolderItem do @moduledoc false use Ash.Resource, diff --git a/test/support/complex_calculations/resources/skill.ex b/test/support/complex_calculations/resources/skill.ex index 6c81ceda..44e8dfff 100644 --- a/test/support/complex_calculations/resources/skill.ex +++ b/test/support/complex_calculations/resources/skill.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ComplexCalculations.Skill do @moduledoc false use Ash.Resource, diff --git a/test/support/concat.ex b/test/support/concat.ex index 353bbfa4..50b34373 100644 --- a/test/support/concat.ex +++ b/test/support/concat.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Concat do @moduledoc false use Ash.Resource.Calculation diff --git a/test/support/dev_test_repo.ex b/test/support/dev_test_repo.ex index 2b1fe929..c69abe43 100644 --- a/test/support/dev_test_repo.ex +++ b/test/support/dev_test_repo.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.DevTestRepo do @moduledoc false use AshPostgres.Repo, diff --git a/test/support/domain.ex b/test/support/domain.ex index ec992cbe..ecd82d96 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Domain do @moduledoc false use Ash.Domain diff --git a/test/support/multi_domain_calculations/domain_one.ex b/test/support/multi_domain_calculations/domain_one.ex index ff4e072f..b5e091f4 100644 --- a/test/support/multi_domain_calculations/domain_one.ex +++ b/test/support/multi_domain_calculations/domain_one.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne do @moduledoc false use Ash.Domain diff --git a/test/support/multi_domain_calculations/domain_one/item.ex b/test/support/multi_domain_calculations/domain_one/item.ex index 36090798..fa07ac9a 100644 --- a/test/support/multi_domain_calculations/domain_one/item.ex +++ b/test/support/multi_domain_calculations/domain_one/item.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainOne.Item do @moduledoc false diff --git a/test/support/multi_domain_calculations/domain_three.ex b/test/support/multi_domain_calculations/domain_three.ex index 95eda02d..a452e013 100644 --- a/test/support/multi_domain_calculations/domain_three.ex +++ b/test/support/multi_domain_calculations/domain_three.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree do @moduledoc false use Ash.Domain diff --git a/test/support/multi_domain_calculations/domain_three/relationship_item.ex b/test/support/multi_domain_calculations/domain_three/relationship_item.ex index b90b93a7..9c15eb0c 100644 --- a/test/support/multi_domain_calculations/domain_three/relationship_item.ex +++ b/test/support/multi_domain_calculations/domain_three/relationship_item.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainThree.RelationshipItem do @moduledoc false diff --git a/test/support/multi_domain_calculations/domain_two.ex b/test/support/multi_domain_calculations/domain_two.ex index f9ccde38..715960fb 100644 --- a/test/support/multi_domain_calculations/domain_two.ex +++ b/test/support/multi_domain_calculations/domain_two.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo do @moduledoc false use Ash.Domain diff --git a/test/support/multi_domain_calculations/domain_two/other_item.ex b/test/support/multi_domain_calculations/domain_two/other_item.ex index 1524b47d..b7e65fd7 100644 --- a/test/support/multi_domain_calculations/domain_two/other_item.ex +++ b/test/support/multi_domain_calculations/domain_two/other_item.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.OtherItem do @moduledoc false diff --git a/test/support/multi_domain_calculations/domain_two/sub_item.ex b/test/support/multi_domain_calculations/domain_two/sub_item.ex index c9f8b491..76550194 100644 --- a/test/support/multi_domain_calculations/domain_two/sub_item.ex +++ b/test/support/multi_domain_calculations/domain_two/sub_item.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.MultiDomainCalculations.DomainTwo.SubItem do @moduledoc false diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 6c1db197..1ee5e90b 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.Domain do @moduledoc false use Ash.Domain diff --git a/test/support/multitenancy/resources/composite_key_post.ex b/test/support/multitenancy/resources/composite_key_post.ex index 84f7881a..b7856998 100644 --- a/test/support/multitenancy/resources/composite_key_post.ex +++ b/test/support/multitenancy/resources/composite_key_post.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.CompositeKeyPost do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/cross_tenant_post_link.ex b/test/support/multitenancy/resources/cross_tenant_post_link.ex index 0bcc7111..e79e0cf8 100644 --- a/test/support/multitenancy/resources/cross_tenant_post_link.ex +++ b/test/support/multitenancy/resources/cross_tenant_post_link.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.CrossTenantPostLink do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/dev_migrations_org.ex b/test/support/multitenancy/resources/dev_migrations_org.ex index 6ebd3394..0cc59783 100644 --- a/test/support/multitenancy/resources/dev_migrations_org.ex +++ b/test/support/multitenancy/resources/dev_migrations_org.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.DevMigrationsOrg do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/named_org.ex b/test/support/multitenancy/resources/named_org.ex index 537ea337..714c090d 100644 --- a/test/support/multitenancy/resources/named_org.ex +++ b/test/support/multitenancy/resources/named_org.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.NamedOrg do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/non_multitenant_post_link.ex b/test/support/multitenancy/resources/non_multitenant_post_link.ex index 9d986bdb..8f2a990e 100644 --- a/test/support/multitenancy/resources/non_multitenant_post_link.ex +++ b/test/support/multitenancy/resources/non_multitenant_post_link.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.NonMultitenantPostLink do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex index 56dd8d26..0eb78386 100644 --- a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex +++ b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.NonMultitenantPostMultitenantLink do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index aa68daf4..21638793 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.Org do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index 6f9a943a..cab27827 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.Post do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/post_link.ex b/test/support/multitenancy/resources/post_link.ex index bd22c558..dcdfdc41 100644 --- a/test/support/multitenancy/resources/post_link.ex +++ b/test/support/multitenancy/resources/post_link.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.PostLink do @moduledoc false use Ash.Resource, diff --git a/test/support/multitenancy/resources/user.ex b/test/support/multitenancy/resources/user.ex index 77149182..7406eb07 100644 --- a/test/support/multitenancy/resources/user.ex +++ b/test/support/multitenancy/resources/user.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.MultitenancyTest.User do @moduledoc false use Ash.Resource, diff --git a/test/support/relationships/comments_containing_title.ex b/test/support/relationships/comments_containing_title.ex index a58a6b93..e17ee933 100644 --- a/test/support/relationships/comments_containing_title.ex +++ b/test/support/relationships/comments_containing_title.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Post.CommentsContainingTitle do @moduledoc false diff --git a/test/support/repo_case.ex b/test/support/repo_case.ex index 28d9f3d2..708e2d42 100644 --- a/test/support/repo_case.ex +++ b/test/support/repo_case.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.RepoCase do @moduledoc false use ExUnit.CaseTemplate diff --git a/test/support/resources/account.ex b/test/support/resources/account.ex index c771b9ce..87e6544a 100644 --- a/test/support/resources/account.ex +++ b/test/support/resources/account.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Account do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index e30f7592..097a771c 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Author do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/bio.ex b/test/support/resources/bio.ex index 59dd2dfb..8c0b33f0 100644 --- a/test/support/resources/bio.ex +++ b/test/support/resources/bio.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Bio do @moduledoc false use Ash.Resource, data_layer: :embedded diff --git a/test/support/resources/chat.ex b/test/support/resources/chat.ex index 9a035f4c..5c04c1af 100644 --- a/test/support/resources/chat.ex +++ b/test/support/resources/chat.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Chat do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/co_authored_post.ex b/test/support/resources/co_authored_post.ex index 5519144b..022c9eca 100644 --- a/test/support/resources/co_authored_post.ex +++ b/test/support/resources/co_authored_post.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CoAuthorPost do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/comedian.ex b/test/support/resources/comedian.ex index a61f3c41..2404d56c 100644 --- a/test/support/resources/comedian.ex +++ b/test/support/resources/comedian.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Comedian do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index 517c42af..cbbc6abb 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Comment do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/comment_link.ex b/test/support/resources/comment_link.ex index 2654ff0f..5caab846 100644 --- a/test/support/resources/comment_link.ex +++ b/test/support/resources/comment_link.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CommentLink do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/content.ex b/test/support/resources/content.ex index 370d7016..8b823644 100644 --- a/test/support/resources/content.ex +++ b/test/support/resources/content.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Content do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/content_visibility_group.ex b/test/support/resources/content_visibility_group.ex index d618fcb4..797d7c7e 100644 --- a/test/support/resources/content_visibility_group.ex +++ b/test/support/resources/content_visibility_group.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.ContentVisibilityGroup do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/csv.ex b/test/support/resources/csv.ex index 91894ed4..ccf5b418 100644 --- a/test/support/resources/csv.ex +++ b/test/support/resources/csv.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CSVColumnMatchingEmbedded do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/customer.ex b/test/support/resources/customer.ex index d5cff43e..834fcead 100644 --- a/test/support/resources/customer.ex +++ b/test/support/resources/customer.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Customer do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/db_point.ex b/test/support/resources/db_point.ex index 25b9ca93..cf018f4a 100644 --- a/test/support/resources/db_point.ex +++ b/test/support/resources/db_point.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.DbPoint do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/db_string_point.ex b/test/support/resources/db_string_point.ex index 8908527b..e78c7536 100644 --- a/test/support/resources/db_string_point.ex +++ b/test/support/resources/db_string_point.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.DbStringPoint do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/entity.ex b/test/support/resources/entity.ex index 732f760f..c693d02a 100644 --- a/test/support/resources/entity.ex +++ b/test/support/resources/entity.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Entity do @moduledoc false diff --git a/test/support/resources/integer_post.ex b/test/support/resources/integer_post.ex index 13b37b4b..2237e723 100644 --- a/test/support/resources/integer_post.ex +++ b/test/support/resources/integer_post.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.IntegerPost do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/invite.ex b/test/support/resources/invite.ex index 32406d1f..06d3ea70 100644 --- a/test/support/resources/invite.ex +++ b/test/support/resources/invite.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Invite do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/joke.ex b/test/support/resources/joke.ex index 7f6318f8..16414cb4 100644 --- a/test/support/resources/joke.ex +++ b/test/support/resources/joke.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Joke do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/manager.ex b/test/support/resources/manager.ex index 43105c21..e5b2cce9 100644 --- a/test/support/resources/manager.ex +++ b/test/support/resources/manager.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Manager do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/message.ex b/test/support/resources/message.ex index 25896e04..4cbc9533 100644 --- a/test/support/resources/message.ex +++ b/test/support/resources/message.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Message do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/note.ex b/test/support/resources/note.ex index b5880dfd..0facb927 100644 --- a/test/support/resources/note.ex +++ b/test/support/resources/note.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Note do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/order.ex b/test/support/resources/order.ex index 27566cb4..fead194c 100644 --- a/test/support/resources/order.ex +++ b/test/support/resources/order.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Order do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index 0013f909..d375a2db 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Organization do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/permalink.ex b/test/support/resources/permalink.ex index 21df7959..adca302f 100644 --- a/test/support/resources/permalink.ex +++ b/test/support/resources/permalink.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Permalink do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 69791a38..2b85570a 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule PassIfOriginalDataPresent do @moduledoc false use Ash.Policy.SimpleCheck diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex index c00ef226..9e71b398 100644 --- a/test/support/resources/post_follower.ex +++ b/test/support/resources/post_follower.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PostFollower do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/post_link.ex b/test/support/resources/post_link.ex index 910486b2..bc6390c8 100644 --- a/test/support/resources/post_link.ex +++ b/test/support/resources/post_link.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PostLink do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/post_tag.ex b/test/support/resources/post_tag.ex index c1cb13e8..2ae47876 100644 --- a/test/support/resources/post_tag.ex +++ b/test/support/resources/post_tag.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PostTag do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/post_views.ex b/test/support/resources/post_views.ex index 929fbb45..c3b3aaaa 100644 --- a/test/support/resources/post_views.ex +++ b/test/support/resources/post_views.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PostView do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/post_with_empty_update.ex b/test/support/resources/post_with_empty_update.ex index da61629d..06693738 100644 --- a/test/support/resources/post_with_empty_update.ex +++ b/test/support/resources/post_with_empty_update.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PostWithEmptyUpdate do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/product.ex b/test/support/resources/product.ex index 199bbff8..fa2bbee9 100644 --- a/test/support/resources/product.ex +++ b/test/support/resources/product.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Product do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/profile.ex b/test/support/resources/profile.ex index 862887c4..b06bab6f 100644 --- a/test/support/resources/profile.ex +++ b/test/support/resources/profile.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Profile do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/punchline.ex b/test/support/resources/punchline.ex index 90e905d9..66da6063 100644 --- a/test/support/resources/punchline.ex +++ b/test/support/resources/punchline.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Punchline do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/rating.ex b/test/support/resources/rating.ex index 99509206..1fab32b1 100644 --- a/test/support/resources/rating.ex +++ b/test/support/resources/rating.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Rating do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index 804c981a..0045db83 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Record do @moduledoc false diff --git a/test/support/resources/record_temp_entity.ex b/test/support/resources/record_temp_entity.ex index 7750a1df..ba0f73e3 100644 --- a/test/support/resources/record_temp_entity.ex +++ b/test/support/resources/record_temp_entity.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.RecordTempEntity do @moduledoc false diff --git a/test/support/resources/role.ex b/test/support/resources/role.ex index 5ce35a73..df3635be 100644 --- a/test/support/resources/role.ex +++ b/test/support/resources/role.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Role do @moduledoc false diff --git a/test/support/resources/rsvp.ex b/test/support/resources/rsvp.ex index 965f053d..f364c333 100644 --- a/test/support/resources/rsvp.ex +++ b/test/support/resources/rsvp.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.RSVP do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/settings.ex b/test/support/resources/settings.ex index b24b3afc..e348f847 100644 --- a/test/support/resources/settings.ex +++ b/test/support/resources/settings.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Settings do @moduledoc false use Ash.Resource, data_layer: :embedded diff --git a/test/support/resources/staff_group.ex b/test/support/resources/staff_group.ex index 3902ca07..4aafeca8 100644 --- a/test/support/resources/staff_group.ex +++ b/test/support/resources/staff_group.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.StaffGroup do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/staff_group_member.ex b/test/support/resources/staff_group_member.ex index 7624a9ce..a9ce9e97 100644 --- a/test/support/resources/staff_group_member.ex +++ b/test/support/resources/staff_group_member.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.StaffGroupMember do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/standup_club.ex b/test/support/resources/standup_club.ex index 3dd2e075..ffa91044 100644 --- a/test/support/resources/standup_club.ex +++ b/test/support/resources/standup_club.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.StandupClub do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/stateful_post_follwer.ex b/test/support/resources/stateful_post_follwer.ex index b6fd8dab..cb66359c 100644 --- a/test/support/resources/stateful_post_follwer.ex +++ b/test/support/resources/stateful_post_follwer.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.StatefulPostFollower do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/subquery/access.ex b/test/support/resources/subquery/access.ex index 90a356ff..9c971486 100644 --- a/test/support/resources/subquery/access.ex +++ b/test/support/resources/subquery/access.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Subquery.Access do @moduledoc false alias AshPostgres.Test.Subquery.Parent diff --git a/test/support/resources/subquery/child.ex b/test/support/resources/subquery/child.ex index cf089bd5..5a9cd6af 100644 --- a/test/support/resources/subquery/child.ex +++ b/test/support/resources/subquery/child.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Subquery.Child do @moduledoc false alias AshPostgres.Test.Subquery.Through diff --git a/test/support/resources/subquery/child_domain.ex b/test/support/resources/subquery/child_domain.ex index 7e427331..ab33df34 100644 --- a/test/support/resources/subquery/child_domain.ex +++ b/test/support/resources/subquery/child_domain.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Subquery.ChildDomain do @moduledoc false alias AshPostgres.Test.Subquery.Child diff --git a/test/support/resources/subquery/parent.ex b/test/support/resources/subquery/parent.ex index 9de04b9f..a72c0054 100644 --- a/test/support/resources/subquery/parent.ex +++ b/test/support/resources/subquery/parent.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Subquery.Parent do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/subquery/parent_domain.ex b/test/support/resources/subquery/parent_domain.ex index 9d73f6af..d2e200d3 100644 --- a/test/support/resources/subquery/parent_domain.ex +++ b/test/support/resources/subquery/parent_domain.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Subquery.ParentDomain do @moduledoc false alias AshPostgres.Test.Subquery.Access diff --git a/test/support/resources/subquery/through.ex b/test/support/resources/subquery/through.ex index 7f1e96e9..3c0f4e75 100644 --- a/test/support/resources/subquery/through.ex +++ b/test/support/resources/subquery/through.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Subquery.Through do @moduledoc false alias AshPostgres.Test.Subquery.Child diff --git a/test/support/resources/tag.ex b/test/support/resources/tag.ex index 20d8d5a8..66bec825 100644 --- a/test/support/resources/tag.ex +++ b/test/support/resources/tag.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Tag do @moduledoc false use Ash.Resource, diff --git a/test/support/resources/temp_entity.ex b/test/support/resources/temp_entity.ex index eb29bf1c..5507836e 100644 --- a/test/support/resources/temp_entity.ex +++ b/test/support/resources/temp_entity.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.TempEntity do @moduledoc false diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index b07b3e7c..1009dccd 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.User do @moduledoc false use Ash.Resource, diff --git a/test/support/string_agg.ex b/test/support/string_agg.ex index 53a6a950..e6f92821 100644 --- a/test/support/string_agg.ex +++ b/test/support/string_agg.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.StringAgg do @moduledoc false use Ash.Resource.Aggregate.CustomAggregate diff --git a/test/support/test_app.ex b/test/support/test_app.ex index 94c990d6..97d98fc2 100644 --- a/test/support/test_app.ex +++ b/test/support/test_app.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestApp do @moduledoc false def start(_type, _args) do diff --git a/test/support/test_custom_extension.ex b/test/support/test_custom_extension.ex index fba66809..bd6d26d3 100644 --- a/test/support/test_custom_extension.ex +++ b/test/support/test_custom_extension.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestCustomExtension do @moduledoc false diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index fca4bf13..14ec8160 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestNoSandboxRepo do @moduledoc false use AshPostgres.Repo, diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 351c94ac..8dbc64c1 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.TestRepo do @moduledoc false use AshPostgres.Repo, diff --git a/test/support/trigram_word_similarity.ex b/test/support/trigram_word_similarity.ex index 4b7d0bfb..77c267c7 100644 --- a/test/support/trigram_word_similarity.ex +++ b/test/support/trigram_word_similarity.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Expressions.TrigramWordSimilarity do @moduledoc false use Ash.CustomExpression, diff --git a/test/support/types/composite_point.ex b/test/support/types/composite_point.ex index 6b6f37dc..b82887b9 100644 --- a/test/support/types/composite_point.ex +++ b/test/support/types/composite_point.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.CompositePoint do @moduledoc false use Ash.Type diff --git a/test/support/types/email.ex b/test/support/types/email.ex index c14190d5..9f552b6d 100644 --- a/test/support/types/email.ex +++ b/test/support/types/email.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule Test.Support.Types.Email do @moduledoc false use Ash.Type.NewType, diff --git a/test/support/types/money.ex b/test/support/types/money.ex index c1569be4..6b2d79f8 100644 --- a/test/support/types/money.ex +++ b/test/support/types/money.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Money do @moduledoc false use Ash.Resource, diff --git a/test/support/types/person_detail.ex b/test/support/types/person_detail.ex index 551d17d5..3666300f 100644 --- a/test/support/types/person_detail.ex +++ b/test/support/types/person_detail.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.PersonDetail do @moduledoc """ A tuple type for testing Ash.Type.Tuple diff --git a/test/support/types/point.ex b/test/support/types/point.ex index ea3db339..819d0287 100644 --- a/test/support/types/point.ex +++ b/test/support/types/point.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Point do @moduledoc false use Ash.Type diff --git a/test/support/types/response.ex b/test/support/types/response.ex index 51f475e0..9428ee15 100644 --- a/test/support/types/response.ex +++ b/test/support/types/response.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Types.Response do @moduledoc false use Ash.Type diff --git a/test/support/types/status.ex b/test/support/types/status.ex index 2aa3e4a7..7453e955 100644 --- a/test/support/types/status.ex +++ b/test/support/types/status.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Types.Status do @moduledoc false use Ash.Type.Enum, values: [:open, :closed] diff --git a/test/support/types/status_enum.ex b/test/support/types/status_enum.ex index dfd46a25..2a845ff2 100644 --- a/test/support/types/status_enum.ex +++ b/test/support/types/status_enum.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Types.StatusEnum do @moduledoc false use Ash.Type.Enum, values: [:open, :closed] diff --git a/test/support/types/status_enum_no_cast.ex b/test/support/types/status_enum_no_cast.ex index ec4a5e6b..032266de 100644 --- a/test/support/types/status_enum_no_cast.ex +++ b/test/support/types/status_enum_no_cast.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.Types.StatusEnumNoCast do @moduledoc false use Ash.Type.Enum, values: [:open, :closed] diff --git a/test/support/types/string_point.ex b/test/support/types/string_point.ex index 0550a904..1be38e28 100644 --- a/test/support/types/string_point.ex +++ b/test/support/types/string_point.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.StringPoint do @moduledoc false use Ash.Type diff --git a/test/support/unrelated_aggregates/profile.ex b/test/support/unrelated_aggregates/profile.ex index 87a9fa9f..257a44e3 100644 --- a/test/support/unrelated_aggregates/profile.ex +++ b/test/support/unrelated_aggregates/profile.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UnrelatedAggregatesTest.Profile do @moduledoc false use Ash.Resource, diff --git a/test/support/unrelated_aggregates/report.ex b/test/support/unrelated_aggregates/report.ex index 652df03c..025e550c 100644 --- a/test/support/unrelated_aggregates/report.ex +++ b/test/support/unrelated_aggregates/report.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UnrelatedAggregatesTest.Report do @moduledoc false use Ash.Resource, diff --git a/test/support/unrelated_aggregates/secure_profile.ex b/test/support/unrelated_aggregates/secure_profile.ex index 3f50bf73..7f380da2 100644 --- a/test/support/unrelated_aggregates/secure_profile.ex +++ b/test/support/unrelated_aggregates/secure_profile.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UnrelatedAggregatesTest.SecureProfile do @moduledoc false use Ash.Resource, diff --git a/test/support/unrelated_aggregates/user.ex b/test/support/unrelated_aggregates/user.ex index ce143489..22529079 100644 --- a/test/support/unrelated_aggregates/user.ex +++ b/test/support/unrelated_aggregates/user.ex @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UnrelatedAggregatesTest.User do @moduledoc false use Ash.Resource, diff --git a/test/test_helper.exs b/test/test_helper.exs index 6745f6eb..aba4a697 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + ExUnit.start(capture_log: true) Logger.configure(level: :debug) diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 603fe0df..a4403a16 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.TransactionTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/tuple_test.exs b/test/tuple_test.exs index 1233ac28..5901f5c4 100644 --- a/test/tuple_test.exs +++ b/test/tuple_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.TupleTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/type_test.exs b/test/type_test.exs index 6355fe92..2e7c3132 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.TypeTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/unique_identity_test.exs b/test/unique_identity_test.exs index 47638025..618ead15 100644 --- a/test/unique_identity_test.exs +++ b/test/unique_identity_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UniqueIdentityTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Organization diff --git a/test/unrelated_aggregates_test.exs b/test/unrelated_aggregates_test.exs index 0ec55535..1c4ca107 100644 --- a/test/unrelated_aggregates_test.exs +++ b/test/unrelated_aggregates_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UnrelatedAggregatesTest do @moduledoc false use AshPostgres.RepoCase, async: false diff --git a/test/update_test.exs b/test/update_test.exs index d05f9863..22fb4af2 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.UpdateTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/test/upsert_test.exs b/test/upsert_test.exs index 08058701..0f3308a7 100644 --- a/test/upsert_test.exs +++ b/test/upsert_test.exs @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Zach Daniel +# +# SPDX-License-Identifier: MIT + defmodule AshPostgres.Test.UpsertTest do use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post diff --git a/usage-rules.md b/usage-rules.md index 632fc52d..84e0c138 100644 --- a/usage-rules.md +++ b/usage-rules.md @@ -1,3 +1,9 @@ + + # Rules for working with AshPostgres ## Understanding AshPostgres From efc4e38a6c1e43d713fa0ae2356fa53d1102898c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:54:36 -0400 Subject: [PATCH 1195/1215] chore(deps): bump ash in the production-dependencies group (#636) Bumps the production-dependencies group with 1 update: [ash](https://github.com/ash-project/ash). Updates `ash` from 3.5.43 to 3.6.2 - [Release notes](https://github.com/ash-project/ash/releases) - [Changelog](https://github.com/ash-project/ash/blob/main/CHANGELOG.md) - [Commits](https://github.com/ash-project/ash/compare/v3.5.43...v3.6.2) --- updated-dependencies: - dependency-name: ash dependency-version: 3.6.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 1827dcb4..05bd993c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.5.43", "222f9a8ac26ad3b029f8e69306cc83091c992d858b4538af12e33a148f301cab", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "48b2aa274c524f5b968c563dd56aec8f9b278c529c8aa46e6fe0ca564c26cc1c"}, + "ash": {:hex, :ash, "3.6.2", "90d1c8296be777b90caabf51b99323d6618a0b92594dfab92b02bdf848ac38bf", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3546b5798dd24576cc451f6e03f3d6e3bb62666c0921bfe8aae700c599d9c38d"}, "ash_sql": {:hex, :ash_sql, "0.3.2", "e2d65dac1c813cbd2569a750bf1c063109778e840052e44535ced294d7638a19", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1f6e5d827c0eb55fc5a07f58eb97f9bb3e6b290d83df75883f422537b98c9c68"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From af0903f1380468bbe1b61fe65acb33772b5e24a5 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Tue, 14 Oct 2025 02:03:13 +0200 Subject: [PATCH 1196/1215] fix: return skipped upserts in bulk_create (#626) --- lib/data_layer.ex | 125 ++++++++++++++++++++++++++++++++------ mix.exs | 4 +- test/bulk_create_test.exs | 61 +++++++++++++++++++ 3 files changed, 169 insertions(+), 21 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 07812631..9755eedf 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -662,6 +662,7 @@ defmodule AshPostgres.DataLayer do def can?(_, :combine), do: true def can?(_, {:combine, _}), do: true def can?(_, :bulk_create), do: true + def can?(_, :bulk_upsert_return_skipped), do: true def can?(_, :action_select), do: true @@ -2035,6 +2036,74 @@ defmodule AshPostgres.DataLayer do repo.insert_all(source, ecto_changesets, opts) end) + identity = options[:identity] + keys = Map.get(identity || %{}, :keys) || Ash.Resource.Info.primary_key(resource) + + # if it's single the return_skipped_upsert? is handled at the + # call site https://github.com/ash-project/ash_postgres/blob/0b21d4a99cc3f6d8676947e291ac9b9d57ad6e2e/lib/data_layer.ex#L3046-L3046 + result = + if options[:return_skipped_upsert?] && !opts[:single?] do + [changeset | _] = changesets + + results_by_identity = + result + |> elem(1) + |> List.wrap() + |> Enum.into(%{}, fn r -> + {Map.take(r, keys), r} + end) + + ash_query = + resource + |> Ash.Query.do_filter( + or: + changesets + |> Enum.filter(fn changeset -> + not Map.has_key?( + results_by_identity, + Map.take(changeset.attributes, keys) + ) + end) + |> Enum.map(fn changeset -> + changeset.attributes + |> Map.take(keys) + |> Keyword.new() + end) + ) + |> then(fn + query when is_nil(identity) or is_nil(identity.where) -> query + query -> Ash.Query.do_filter(query, identity.where) + end) + |> Ash.Query.set_tenant(changeset.tenant) + + skipped_upserts = + with {:ok, ecto_query} <- Ash.Query.data_layer_query(ash_query), + {:ok, results} <- run_query(ecto_query, resource) do + results + |> Enum.map(fn result -> + Ash.Resource.put_metadata(result, :upsert_skipped, true) + end) + |> Enum.reduce(%{}, fn r, acc -> + Map.put(acc, Map.take(r, keys), r) + end) + end + + results = + changesets + |> Enum.map(fn changeset -> + identity = + changeset.attributes + |> Map.take(keys) + + Map.get(results_by_identity, identity, Map.get(skipped_upserts, identity)) + end) + |> Enum.filter(& &1) + + {length(results), results} + else + result + end + case result do {_, nil} -> :ok @@ -2045,25 +2114,43 @@ defmodule AshPostgres.DataLayer do {:ok, results} else - {:ok, - Stream.zip_with(results, changesets, fn result, changeset -> - if !opts[:upsert?] do - maybe_create_tenant!(resource, result) - end - - case get_bulk_operation_metadata(changeset) do - {index, metadata_key} -> - Ash.Resource.put_metadata(result, metadata_key, index) - - nil -> - # Compatibility fallback - Ash.Resource.put_metadata( - result, - :bulk_create_index, - changeset.context[:bulk_create][:index] - ) - end - end)} + results_by_identity = + results + |> Enum.into(%{}, fn r -> + {Map.take(r, keys), r} + end) + + results = + changesets + |> Enum.map(fn changeset -> + identity = + changeset.attributes + |> Map.take(keys) + + result_for_changeset = Map.get(results_by_identity, identity) + + if result_for_changeset do + if !opts[:upsert?] do + maybe_create_tenant!(resource, result_for_changeset) + end + + case get_bulk_operation_metadata(changeset) do + {index, metadata_key} -> + Ash.Resource.put_metadata(result_for_changeset, metadata_key, index) + + nil -> + # Compatibility fallback + Ash.Resource.put_metadata( + result_for_changeset, + :bulk_create_index, + changeset.context[:bulk_create][:index] + ) + end + end + end) + |> Enum.filter(& &1) + + {:ok, results} end end rescue diff --git a/mix.exs b/mix.exs index cf59bf71..af4055bf 100644 --- a/mix.exs +++ b/mix.exs @@ -177,10 +177,10 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.5 and >= 3.5.35")}, + {:ash, ash_version("~> 3.5 and >= 3.6.2")}, {:spark, "~> 2.3 and >= 2.3.4"}, {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.2")}, - {:igniter, "~> 0.6 and >= 0.6.14", optional: true}, + {:igniter, "~> 0.6 and >= 0.6.29", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, {:jason, "~> 1.0"}, diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index e48d0238..420ff219 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -176,6 +176,67 @@ defmodule AshPostgres.BulkCreateTest do end) end + test "bulk upsert returns skipped records with return_skipped_upsert?" do + assert [ + {:ok, %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}}, + {:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}}, + {:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}} + ] = + Ash.bulk_create!( + [ + %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}, + %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}, + %{title: "herbert", uniq_if_contains_foo: "3", price: 30} + ], + Post, + :create, + return_stream?: true, + return_records?: true + ) + |> Enum.sort_by(fn {:ok, result} -> result.title end) + + results = + Ash.bulk_create!( + [ + %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}, + %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000}, + %{title: "herbert", uniq_if_contains_foo: "3", price: 30} + ], + Post, + :upsert_with_no_filter, + return_stream?: true, + upsert_condition: expr(price != upsert_conflict(:price)), + return_errors?: true, + return_records?: true, + return_skipped_upsert?: true + ) + |> Enum.sort_by(fn + {:ok, result} -> + result.title + + _ -> + nil + end) + + assert [ + {:ok, skipped}, + {:ok, updated}, + {:ok, no_conflict} + ] = results + + assert skipped.title == "fredfoo" + assert skipped.price == 10 + assert Ash.Resource.get_metadata(skipped, :upsert_skipped) == true + + assert updated.title == "georgefoo" + assert updated.price == 20_000 + refute Ash.Resource.get_metadata(updated, :upsert_skipped) + + assert no_conflict.title == "herbert" + assert no_conflict.price == 30 + refute Ash.Resource.get_metadata(no_conflict, :upsert_skipped) + end + # confirmed that this doesn't work because it can't. An upsert must map to a potentially successful insert. # leaving this test here for posterity # test "bulk creates can upsert with id" do From 10fa4c981476ee38ef355b34e8c8cc46aca474d1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Oct 2025 22:28:40 -0400 Subject: [PATCH 1197/1215] improvement: leverage new aggregate loading optimization --- lib/data_layer.ex | 13 ++--- mix.lock | 6 +- test/aggregate_test.exs | 126 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 11 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 9755eedf..637b21f4 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -3619,14 +3619,11 @@ defmodule AshPostgres.DataLayer do end @impl true - def add_aggregates(query, aggregates, resource) do - AshSql.Aggregate.add_aggregates( - query, - aggregates, - resource, - true, - query.__ash_bindings__.root_binding - ) + def add_aggregates(query, aggregates, _resource) do + {:ok, + Map.update!(query, :__ash_bindings__, fn bindings -> + Map.put(bindings, :load_aggregates, aggregates) + end)} end @impl true diff --git a/mix.lock b/mix.lock index 05bd993c..96080205 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.6.2", "90d1c8296be777b90caabf51b99323d6618a0b92594dfab92b02bdf848ac38bf", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3546b5798dd24576cc451f6e03f3d6e3bb62666c0921bfe8aae700c599d9c38d"}, - "ash_sql": {:hex, :ash_sql, "0.3.2", "e2d65dac1c813cbd2569a750bf1c063109778e840052e44535ced294d7638a19", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "1f6e5d827c0eb55fc5a07f58eb97f9bb3e6b290d83df75883f422537b98c9c68"}, + "ash_sql": {:hex, :ash_sql, "0.3.4", "c8c0446fbd6d3e6920f793b971c83ba3d14f96095036366d313b72656400509d", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "4edb1fb707048a41f7944274b1a0b571aa3c9117b8a7a12809ca023b6f955cb4"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -17,7 +17,7 @@ "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, "ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"}, - "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.9.0", "b74f6040084f523055b720cc7ef718da47f2cbe726a5f30c2871118635cb91c1", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "7fdf84be3490e5692c5dc1f8a1084eed47a221c1063e41938c73312f0bfea259"}, @@ -42,7 +42,7 @@ "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, - "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, + "sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spark": {:hex, :spark, "2.3.5", "f30d30ecc3b4ab9b932d9aada66af7677fc1f297a2c349b0bcec3eafb9f996e8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0e9d339704d5d148f77f2b2fef3bcfc873a9e9bb4224fcf289c545d65827202f"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index ff51bf26..3e64440f 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1734,4 +1734,130 @@ defmodule AshSql.AggregateTest do assert loaded_post.count_of_comments == 1 end + + test "aggregate with sort and limit is accurate" do + # Setup: Create an author with multiple posts + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + # Create posts with different titles to test sorting + post1 = + Post + |> Ash.Changeset.for_create(:create, %{title: "A First Post"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{title: "Z Last Post"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + post3 = + Post + |> Ash.Changeset.for_create(:create, %{title: "M Middle Post"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + # Add comments to posts + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment 1"}) + |> Ash.Changeset.manage_relationship(:post, post1, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment 2"}) + |> Ash.Changeset.manage_relationship(:post, post1, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment 3"}) + |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) + |> Ash.create!() + + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment 4"}) + |> Ash.Changeset.manage_relationship(:post, post3, type: :append_and_remove) + |> Ash.create!() + + # Query with aggregate, sort, and limit + # This should ideally use a subquery to apply sort/limit before loading the aggregate + results = + Post + |> Ash.Query.load(:count_of_comments) + |> Ash.Query.sort(:title) + |> Ash.Query.limit(2) + |> Ash.read!() + + # Verify we got the right posts (sorted by title, limited to 2) + assert length(results) == 2 + assert Enum.at(results, 0).title == "A First Post" + assert Enum.at(results, 1).title == "M Middle Post" + + # Verify the aggregates are correct + assert Enum.at(results, 0).count_of_comments == 2 + assert Enum.at(results, 1).count_of_comments == 1 + end + + test "aggregate with sort by aggregate value and limit is accurate" do + # This tests sorting by the aggregate itself, not by another field + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "Jane", last_name: "Smith"}) + |> Ash.create!() + + # Create posts with different numbers of comments + post1 = + Post + |> Ash.Changeset.for_create(:create, %{title: "Post with 3 comments"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + post2 = + Post + |> Ash.Changeset.for_create(:create, %{title: "Post with 1 comment"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + post3 = + Post + |> Ash.Changeset.for_create(:create, %{title: "Post with 2 comments"}) + |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) + |> Ash.create!() + + # Add varying numbers of comments + for _i <- 1..3 do + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment on post 1"}) + |> Ash.Changeset.manage_relationship(:post, post1, type: :append_and_remove) + |> Ash.create!() + end + + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment on post 2"}) + |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) + |> Ash.create!() + + for _i <- 1..2 do + Comment + |> Ash.Changeset.for_create(:create, %{title: "Comment on post 3"}) + |> Ash.Changeset.manage_relationship(:post, post3, type: :append_and_remove) + |> Ash.create!() + end + + # Query sorting by the aggregate value itself + results = + Post + |> Ash.Query.load(:count_of_comments) + |> Ash.Query.sort(count_of_comments: :desc) + |> Ash.Query.limit(2) + |> Ash.read!() + + # Should get the posts with most comments first + assert length(results) == 2 + assert Enum.at(results, 0).count_of_comments == 3 + assert Enum.at(results, 1).count_of_comments == 2 + end end From d011d0ef554b5d9be747965d32e3bcd87b82bcb4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 13 Oct 2025 22:55:09 -0400 Subject: [PATCH 1198/1215] chore: release version v2.6.22 --- CHANGELOG.md | 13 +++++++++++++ mix.exs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b045424..67570853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,19 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.22](https://github.com/ash-project/ash_postgres/compare/v2.6.21...v2.6.22) (2025-10-14) + + + + +### Bug Fixes: + +* return skipped upserts in bulk_create (#626) by Barnabas Jovanovics + +### Improvements: + +* leverage new aggregate loading optimization by Zach Daniel + ## [v2.6.21](https://github.com/ash-project/ash_postgres/compare/v2.6.20...v2.6.21) (2025-10-10) diff --git a/mix.exs b/mix.exs index af4055bf..0bd7385a 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.21" + @version "2.6.22" def project do [ From 4da5f3b00cdf6b3d2d0292659494ecebb5a2928c Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 15 Oct 2025 08:43:52 +1300 Subject: [PATCH 1199/1215] chore: Fix REUSE copyright attributions --- .check.exs | 2 +- .credo.exs | 2 +- .formatter.exs | 2 +- .github/dependabot.yml | 2 +- .github/workflows/elixir.yml | 2 +- .gitignore | 2 +- .tool-versions.license | 2 +- .vscode/settings.json.license | 2 +- benchmarks/bulk_create.exs | 2 +- config/config.exs | 2 +- documentation/dsls/DSL-AshPostgres.DataLayer.md.license | 2 +- lib/ash_postgres.ex | 2 +- lib/check_constraint.ex | 2 +- lib/custom_aggregate.ex | 2 +- lib/custom_extension.ex | 2 +- lib/custom_index.ex | 2 +- lib/data_layer.ex | 2 +- lib/data_layer/info.ex | 2 +- lib/ecto_migration_default.ex | 2 +- lib/extensions/immutable_raise_error.ex | 2 +- lib/extensions/vector.ex | 2 +- lib/functions/binding.ex | 2 +- lib/functions/ilike.ex | 2 +- lib/functions/like.ex | 2 +- lib/functions/trigram_similarity.ex | 2 +- lib/functions/vector_cosine_distance.ex | 2 +- lib/functions/vector_l2_distance.ex | 2 +- lib/igniter.ex | 2 +- lib/manual_relationship.ex | 2 +- lib/migration.ex | 2 +- lib/migration_compile_cache.ex | 2 +- lib/migration_generator/ash_functions.ex | 2 +- lib/migration_generator/migration_generator.ex | 2 +- lib/migration_generator/operation.ex | 2 +- lib/migration_generator/phase.ex | 2 +- lib/mix/helpers.ex | 2 +- lib/mix/tasks/ash_postgres.create.ex | 2 +- lib/mix/tasks/ash_postgres.drop.ex | 2 +- lib/mix/tasks/ash_postgres.gen.resources.ex | 2 +- lib/mix/tasks/ash_postgres.generate_migrations.ex | 2 +- lib/mix/tasks/ash_postgres.install.ex | 2 +- lib/mix/tasks/ash_postgres.migrate.ex | 2 +- lib/mix/tasks/ash_postgres.rollback.ex | 2 +- lib/mix/tasks/ash_postgres.setup_vector.ex | 2 +- lib/mix/tasks/ash_postgres.squash_snapshots.ex | 2 +- lib/multitenancy.ex | 2 +- lib/reference.ex | 2 +- lib/repo.ex | 2 +- lib/repo/before_compile.ex | 2 +- lib/resource_generator/resource_generator.ex | 2 +- lib/resource_generator/sensitive_data.ex | 2 +- lib/resource_generator/spec.ex | 2 +- lib/sql_implementation.ex | 2 +- lib/statement.ex | 2 +- lib/type.ex | 2 +- lib/types/ci_string_wrapper.ex | 2 +- lib/types/ltree.ex | 2 +- lib/types/string_wrapper.ex | 2 +- lib/types/timestamptz.ex | 2 +- lib/types/timestamptz_usec.ex | 2 +- lib/types/tsquery.ex | 2 +- lib/types/tsvector.ex | 2 +- lib/verifiers/ensure_table_or_polymorphic.ex | 2 +- .../prevent_attribute_multitenancy_and_non_full_match_type.ex | 2 +- lib/verifiers/prevent_multidimensional_array_aggregates.ex | 2 +- lib/verifiers/validate_identity_index_names.ex | 2 +- lib/verifiers/validate_references.ex | 2 +- lib/version_agent.ex | 2 +- logos/small-logo.png.license | 2 +- mix.exs | 2 +- mix.lock.license | 2 +- .../20250526214825_migrate_resources_extensions_1.exs | 2 +- .../migrations/20250526214827_migrate_resources1.exs | 2 +- priv/resource_snapshots/dev_test_repo/extensions.json.license | 2 +- .../multitenant_orgs/20250526214827.json.license | 2 +- .../test_no_sandbox_repo/extensions.json.license | 2 +- .../test_repo/accounts/20221217123726.json.license | 2 +- .../test_repo/accounts/20240327211150.json.license | 2 +- .../test_repo/authors/20220805191443.json.license | 2 +- .../test_repo/authors/20220914104733.json.license | 2 +- .../test_repo/authors/20240327211150.json.license | 2 +- .../test_repo/authors/20240705113722.json.license | 2 +- .../test_repo/authors/20250908212414.json.license | 2 +- .../test_repo/chats/20250908093505.json.license | 2 +- .../test_repo/co_authored_posts/20241208221219.json.license | 2 +- .../test_repo/comedians/20241217232254.json.license | 2 +- .../test_repo/comedians/20250413141328.json.license | 2 +- .../test_repo/comment_links/20250123161002.json.license | 2 +- .../test_repo/comment_ratings/20220805191443.json.license | 2 +- .../test_repo/comment_ratings/20240327211150.json.license | 2 +- .../test_repo/comments/20220805191443.json.license | 2 +- .../test_repo/comments/20240327211150.json.license | 2 +- .../test_repo/comments/20240327211917.json.license | 2 +- .../20230816231942.json.license | 2 +- .../20240327211150.json.license | 2 +- .../20231116013020.json.license | 2 +- .../20240327211150.json.license | 2 +- .../20240327211917.json.license | 2 +- .../complex_calculations_channels/20231116013020.json.license | 2 +- .../complex_calculations_channels/20240327211150.json.license | 2 +- .../complex_calculations_channels/20240327211917.json.license | 2 +- .../20230816231942.json.license | 2 +- .../20240327211150.json.license | 2 +- .../20240327211917.json.license | 2 +- .../20250714225304.json.license | 2 +- .../complex_calculations_folders/20250714225304.json.license | 2 +- .../complex_calculations_skills/20230816231942.json.license | 2 +- .../complex_calculations_skills/20240327211150.json.license | 2 +- .../test_repo/content/20250123164209.json.license | 2 +- .../content_visibility_group/20250123164209.json.license | 2 +- .../test_repo/csv/20250320225052.json.license | 2 +- .../test_repo/customers/20250908073737.json.license | 2 +- .../test_repo/entities/20240109160153.json.license | 2 +- .../test_repo/entities/20240327211150.json.license | 2 +- .../test_repo/entities/20240327211917.json.license | 2 +- priv/resource_snapshots/test_repo/extensions.json.license | 2 +- .../test_repo/integer_posts/20220805191443.json.license | 2 +- .../test_repo/items/20240713134055.json.license | 2 +- .../test_repo/items/20240717104854.json.license | 2 +- .../test_repo/items/20240717153736.json.license | 2 +- .../test_repo/jokes/20241217232254.json.license | 2 +- .../test_repo/jokes/20250413141328.json.license | 2 +- .../test_repo/managers/20230526144249.json.license | 2 +- .../test_repo/managers/20240327211150.json.license | 2 +- .../test_repo/messages/20250908093505.json.license | 2 +- .../multitenant_named_orgs/20250519103535.json.license | 2 +- .../test_repo/multitenant_orgs/20220805191443.json.license | 2 +- .../test_repo/multitenant_orgs/20240327211150.json.license | 2 +- .../test_repo/multitenant_orgs/20240627223225.json.license | 2 +- .../test_repo/multitenant_orgs/20240702164513.json.license | 2 +- .../test_repo/multitenant_orgs/20240703155134.json.license | 2 +- .../non_multitenant_post_links/20250122190558.json.license | 2 +- .../test_repo/note/20250123164209.json.license | 2 +- .../test_repo/orders/20250908073737.json.license | 2 +- .../test_repo/orgs/20230129050950.json.license | 2 +- .../test_repo/orgs/20240327211150.json.license | 2 +- .../test_repo/orgs/20250210191116.json.license | 2 +- .../test_repo/other_items/20240713134055.json.license | 2 +- .../test_repo/other_items/20240717151815.json.license | 2 +- .../test_repo/points/20250313112823.json.license | 2 +- .../test_repo/post_followers/20240227180858.json.license | 2 +- .../test_repo/post_followers/20240227181137.json.license | 2 +- .../test_repo/post_followers/20240327211150.json.license | 2 +- .../test_repo/post_followers/20240516205244.json.license | 2 +- .../test_repo/post_followers/20240517223946.json.license | 2 +- .../test_repo/post_links/20220805191443.json.license | 2 +- .../test_repo/post_links/20221017133955.json.license | 2 +- .../test_repo/post_links/20221202194704.json.license | 2 +- .../test_repo/post_links/20240610195853.json.license | 2 +- .../test_repo/post_links/20240617193218.json.license | 2 +- .../test_repo/post_permalinks/20240906170759.json.license | 2 +- .../test_repo/post_ratings/20220805191443.json.license | 2 +- .../test_repo/post_ratings/20240327211150.json.license | 2 +- .../test_repo/post_tags/20250810102512.json.license | 2 +- .../test_repo/post_views/20230905050351.json.license | 2 +- .../test_repo/post_views/20240327211917.json.license | 2 +- .../test_repo/posts/20220805191443.json.license | 2 +- .../test_repo/posts/20221125171150.json.license | 2 +- .../test_repo/posts/20221125171204.json.license | 2 +- .../test_repo/posts/20230129050950.json.license | 2 +- .../test_repo/posts/20230823161017.json.license | 2 +- .../test_repo/posts/20231127215636.json.license | 2 +- .../test_repo/posts/20231129141453.json.license | 2 +- .../test_repo/posts/20231219132807.json.license | 2 +- .../test_repo/posts/20240129221511.json.license | 2 +- .../test_repo/posts/20240224001913.json.license | 2 +- .../test_repo/posts/20240327211150.json.license | 2 +- .../test_repo/posts/20240327211917.json.license | 2 +- .../test_repo/posts/20240503012410.json.license | 2 +- .../test_repo/posts/20240504185511.json.license | 2 +- .../test_repo/posts/20240524031113.json.license | 2 +- .../test_repo/posts/20240524041750.json.license | 2 +- .../test_repo/posts/20240617193218.json.license | 2 +- .../test_repo/posts/20240618102809.json.license | 2 +- .../test_repo/posts/20240712232026.json.license | 2 +- .../test_repo/posts/20240715135403.json.license | 2 +- .../test_repo/posts/20240910180107.json.license | 2 +- .../test_repo/posts/20240911225320.json.license | 2 +- .../test_repo/posts/20240918104740.json.license | 2 +- .../test_repo/posts/20240929121224.json.license | 2 +- .../test_repo/posts/20250217054207.json.license | 2 +- .../test_repo/posts/20250313112823.json.license | 2 +- .../test_repo/posts/20250520130634.json.license | 2 +- .../test_repo/posts/20250521105654.json.license | 2 +- .../test_repo/posts/20250612113920.json.license | 2 +- .../test_repo/posts/20250618011917.json.license | 2 +- .../test_repo/products/20250908073737.json.license | 2 +- .../test_repo/profile/20220805191443.json.license | 2 +- .../test_repo/profiles.profile/20240327211150.json.license | 2 +- .../test_repo/punchlines/20250413141328.json.license | 2 +- .../test_repo/records/20240109160153.json.license | 2 +- .../test_repo/records/20240327211150.json.license | 2 +- .../test_repo/records/20240327211917.json.license | 2 +- .../records_temp_entities/20250605230457.json.license | 2 +- .../test_repo/relationship_items/20240717153736.json.license | 2 +- .../test_repo/rsvps/20251002180954.json.license | 2 +- .../test_repo/schematic_groups/20240821213522.json.license | 2 +- .../test_repo/staff_group/20250123164209.json.license | 2 +- .../test_repo/staff_group_member/20250123164209.json.license | 2 +- .../test_repo/standup_clubs/20250413141328.json.license | 2 +- .../stateful_post_followers/20240618085942.json.license | 2 +- .../test_repo/string_points/20250313112823.json.license | 2 +- .../test_repo/sub_items/20240713134055.json.license | 2 +- .../test_repo/subquery_access/20240130133933.json.license | 2 +- .../test_repo/subquery_child/20240130133933.json.license | 2 +- .../test_repo/subquery_parent/20240130133933.json.license | 2 +- .../test_repo/subquery_through/20240130133933.json.license | 2 +- .../test_repo/tags/20250810102512.json.license | 2 +- .../test_repo/temp.temp_entities/20240327211150.json.license | 2 +- .../test_repo/temp.temp_entities/20240327211917.json.license | 2 +- .../test_repo/temp_entities/20240109160153.json.license | 2 +- .../tenants/composite_key/20250220073135.json.license | 2 +- .../tenants/composite_key/20250220073141.json.license | 2 +- .../cross_tenant_post_links/20250122203454.json.license | 2 +- .../tenants/friend_links/20240610162043.json.license | 2 +- .../tenants/multitenant_posts/20220805191441.json.license | 2 +- .../tenants/multitenant_posts/20240327211149.json.license | 2 +- .../20251001120813.json.license | 2 +- .../test_repo/unrelated_profiles/20250731124648.json.license | 2 +- .../test_repo/unrelated_reports/20250731124648.json.license | 2 +- .../unrelated_secure_profiles/20250731124648.json.license | 2 +- .../test_repo/unrelated_users/20250731124648.json.license | 2 +- .../test_repo/user_invites/20240727145758.json.license | 2 +- .../test_repo/users/20220805191443.json.license | 2 +- .../test_repo/users/20221217123726.json.license | 2 +- .../test_repo/users/20230129050950.json.license | 2 +- .../test_repo/users/20240327211150.json.license | 2 +- .../test_repo/users/20240727145758.json.license | 2 +- .../test_repo/users/20240929124728.json.license | 2 +- .../test_repo/users/20250320225052.json.license | 2 +- .../test_repo/users/20250321142835.json.license | 2 +- priv/test_no_sandbox_repo/migrations/.gitkeep.license | 2 +- .../migrations/20240627223224_install_5_extensions.exs | 2 +- .../20240712232025_install_ash-functions_extension_4.exs | 2 +- .../20250113205301_migrate_resources_extensions_1.exs | 2 +- .../migrations/20220805191440_install_4_extensions.exs | 4 ++-- .../migrations/20220805191443_migrate_resources1.exs | 2 +- .../migrations/20220914104733_migrate_resources2.exs | 4 ++-- .../migrations/20221017133955_migrate_resources3.exs | 4 ++-- .../migrations/20221125171148_migrate_resources4.exs | 4 ++-- .../migrations/20221125171150_migrate_resources5.exs | 4 ++-- .../migrations/20221202194704_migrate_resources6.exs | 4 ++-- .../migrations/20221217123726_migrate_resources7.exs | 4 ++-- .../migrations/20230129050950_migrate_resources8.exs | 4 ++-- .../migrations/20230526144249_migrate_resources9.exs | 4 ++-- .../20230712182523_install_ash-functions_extension.exs | 2 +- .../20230804223759_install_demo-functions_v0_extension.exs | 4 ++-- .../20230804223818_install_demo-functions_v1_extension.exs | 4 ++-- .../20230816231942_add_complex_calculation_tables.exs | 4 ++-- .../migrations/20230823161017_migrate_resources10.exs | 4 ++-- priv/test_repo/migrations/20230905050351_add_post_views.exs | 4 ++-- .../20231116013020_add_complex_calculations_channels.exs | 4 ++-- .../migrations/20231127212608_add_composite_type.exs | 2 +- .../migrations/20231127215636_migrate_resources11.exs | 2 +- .../migrations/20231129141453_migrate_resources12.exs | 4 ++-- .../20231214220937_install_ash-functions_extension_2.exs | 2 +- .../migrations/20231219132807_migrate_resources13.exs | 4 ++-- .../20231231051611_install_ash-functions_extension_3.exs | 4 ++-- .../migrations/20240109155951_create_temp_schema.exs | 2 +- .../migrations/20240109160153_migrate_resources14.exs | 4 ++-- .../migrations/20240129221511_migrate_resources15.exs | 4 ++-- .../20240130133933_add_resources_for_subquery_test.exs | 2 +- .../migrations/20240224001913_migrate_resources16.exs | 4 ++-- .../migrations/20240227180858_migrate_resources17.exs | 4 ++-- .../migrations/20240227181137_migrate_resources18.exs | 4 ++-- .../migrations/20240229050455_install_5_extensions.exs | 2 +- .../migrations/20240327211150_migrate_resources19.exs | 2 +- .../migrations/20240327211917_migrate_resources20.exs | 2 +- .../migrations/20240503012410_migrate_resources21.exs | 2 +- .../migrations/20240504185511_migrate_resources22.exs | 2 +- .../migrations/20240516205244_migrate_resources23.exs | 2 +- .../migrations/20240517223946_migrate_resources24.exs | 2 +- .../migrations/20240524031113_migrate_resources25.exs | 2 +- .../migrations/20240524041750_migrate_resources26.exs | 2 +- .../migrations/20240610195853_migrate_resources27.exs | 2 +- .../migrations/20240617193218_migrate_resources28.exs | 2 +- .../migrations/20240618085942_migrate_resources29.exs | 2 +- .../migrations/20240618102809_migrate_resources30.exs | 2 +- .../20240622192715_install_ash-functions_extension_4.exs | 2 +- .../migrations/20240627223225_migrate_resources31.exs | 2 +- .../migrations/20240703155134_migrate_resources32.exs | 2 +- .../migrations/20240705113722_migrate_resources33.exs | 2 +- .../migrations/20240712232026_migrate_resources34.exs | 2 +- .../migrations/20240713134055_multi_domain_calculations.exs | 2 +- .../migrations/20240715135403_migrate_resources35.exs | 2 +- .../20240717104854_no_attributes_calculation_test.exs | 2 +- .../migrations/20240717151815_migrate_resources36.exs | 2 +- .../migrations/20240717153736_migrate_resources37.exs | 2 +- priv/test_repo/migrations/20240727145758_user_invites.exs | 2 +- .../migrations/20240906170759_migrate_resources38.exs | 2 +- .../migrations/20240910180107_migrate_resources39.exs | 2 +- .../migrations/20240911225319_install_1_extensions.exs | 2 +- .../migrations/20240911225320_migrate_resources40.exs | 2 +- .../migrations/20240918104740_migrate_resources41.exs | 2 +- .../migrations/20240929121224_migrate_resources42.exs | 2 +- .../migrations/20240929124728_migrate_resources43.exs | 2 +- .../migrations/20241208221219_migrate_resources44.exs | 2 +- .../migrations/20241217232254_migrate_resources45.exs | 2 +- .../20250113205259_migrate_resources_extensions_1.exs | 2 +- .../migrations/20250122190558_migrate_resources46.exs | 2 +- .../migrations/20250123161002_migrate_resources47.exs | 2 +- .../migrations/20250123164209_migrate_resources48.exs | 2 +- .../migrations/20250210191116_migrate_resources49.exs | 2 +- .../migrations/20250217054207_migrate_resources50.exs | 2 +- .../migrations/20250313112823_migrate_resources51.exs | 2 +- priv/test_repo/migrations/20250320225052_add_csv_resource.exs | 2 +- .../migrations/20250321142835_migrate_resources52.exs | 2 +- .../20250413141328_add_punchlines_and_standup_clubs.exs | 2 +- .../migrations/20250519103535_migrate_resources53.exs | 2 +- .../migrations/20250520130634_migrate_resources54.exs | 2 +- .../migrations/20250521105654_add_model_tuple_to_post.exs | 2 +- .../20250605230457_create_record_temp_entities_table.exs | 2 +- .../migrations/20250612113920_migrate_resources55.exs | 2 +- .../migrations/20250618011917_migrate_resources56.exs | 2 +- ...250714225304_add_complex_calculations_folder_and_items.exs | 2 +- .../migrations/20250731124648_migrate_resources57.exs | 2 +- .../migrations/20250810102512_migrate_resources58.exs | 2 +- .../migrations/20250908073737_migrate_resources59.exs | 2 +- .../migrations/20250908093505_migrate_resources60.exs | 2 +- .../migrations/20250908212414_migrate_resources61.exs | 2 +- .../migrations/20251002180954_migrate_resources62.exs | 2 +- .../tenant_migrations/20220805191441_migrate_resources1.exs | 4 ++-- .../tenant_migrations/20240327211149_migrate_resources2.exs | 2 +- .../tenant_migrations/20240610162043_migrate_resources3.exs | 2 +- .../tenant_migrations/20250122203454_migrate_resources4.exs | 2 +- .../tenant_migrations/20250220073135_migrate_resources5.exs | 2 +- .../tenant_migrations/20250220073141_migrate_resources6.exs | 2 +- .../tenant_migrations/20251001120813_migrate_resources7.exs | 2 +- test/aggregate_test.exs | 2 +- test/ash_postgres_test.exs | 2 +- test/atomics_test.exs | 2 +- test/bulk_create_test.exs | 2 +- test/bulk_destroy_test.exs | 2 +- test/bulk_update_test.exs | 2 +- test/calculation_test.exs | 2 +- test/cascade_destroy_test.exs | 2 +- test/combination_test.exs | 2 +- test/complex_calculations_test.exs | 2 +- test/composite_type_test.exs | 2 +- test/constraint_test.exs | 2 +- test/create_test.exs | 2 +- test/custom_expression_test.exs | 2 +- test/custom_index_test.exs | 2 +- test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs | 2 +- test/destroy_test.exs | 2 +- test/dev_migrations_test.exs | 2 +- test/distinct_test.exs | 2 +- test/ecto_compatibility_test.exs | 2 +- test/embeddable_resource_test.exs | 2 +- test/enum_test.exs | 2 +- test/error_expr_test.exs | 2 +- .../filter_child_relationship_by_parent_relationship_test.exs | 2 +- test/filter_field_policy_test.exs | 2 +- test/filter_test.exs | 2 +- test/load_test.exs | 2 +- test/lock_test.exs | 2 +- test/ltree_test.exs | 2 +- test/manual_relationships_test.exs | 2 +- test/manual_update_test.exs | 2 +- test/many_to_many_expr_test.exs | 2 +- test/migration_generator_test.exs | 2 +- test/mix/tasks/ash_postgres.install_test.exs | 2 +- test/mix_squash_snapshots_test.exs | 2 +- test/multi_domain_calculations_test.exs | 2 +- test/multitenancy_test.exs | 2 +- test/parent_filter_test.exs | 2 +- test/parent_sort_test.exs | 2 +- test/polymorphism_test.exs | 2 +- test/primary_key_test.exs | 2 +- test/references_test.exs | 2 +- test/rel_with_parent_filter_test.exs | 2 +- test/resource_generator_test.exs | 2 +- test/schema_test.exs | 2 +- test/select_test.exs | 2 +- test/sort_test.exs | 2 +- test/storage_types_test.exs | 2 +- test/subquery_test.exs | 2 +- test/support/complex_calculations/domain.ex | 2 +- test/support/complex_calculations/resources/certification.ex | 2 +- test/support/complex_calculations/resources/channel.ex | 2 +- test/support/complex_calculations/resources/channel_member.ex | 2 +- test/support/complex_calculations/resources/dm_channel.ex | 2 +- test/support/complex_calculations/resources/documentation.ex | 2 +- test/support/complex_calculations/resources/folder.ex | 2 +- test/support/complex_calculations/resources/folder_item.ex | 2 +- test/support/complex_calculations/resources/skill.ex | 2 +- test/support/concat.ex | 2 +- test/support/dev_test_repo.ex | 2 +- test/support/domain.ex | 2 +- test/support/multi_domain_calculations/domain_one.ex | 2 +- test/support/multi_domain_calculations/domain_one/item.ex | 2 +- test/support/multi_domain_calculations/domain_three.ex | 2 +- .../domain_three/relationship_item.ex | 2 +- test/support/multi_domain_calculations/domain_two.ex | 2 +- .../multi_domain_calculations/domain_two/other_item.ex | 2 +- test/support/multi_domain_calculations/domain_two/sub_item.ex | 2 +- test/support/multitenancy/domain.ex | 2 +- test/support/multitenancy/resources/composite_key_post.ex | 2 +- test/support/multitenancy/resources/cross_tenant_post_link.ex | 2 +- test/support/multitenancy/resources/dev_migrations_org.ex | 2 +- test/support/multitenancy/resources/named_org.ex | 2 +- .../multitenancy/resources/non_multitenant_post_link.ex | 2 +- .../resources/non_multitenant_post_multitenant_link.ex | 2 +- test/support/multitenancy/resources/org.ex | 2 +- test/support/multitenancy/resources/post.ex | 2 +- test/support/multitenancy/resources/post_link.ex | 2 +- test/support/multitenancy/resources/user.ex | 2 +- test/support/relationships/comments_containing_title.ex | 2 +- test/support/repo_case.ex | 2 +- test/support/resources/account.ex | 2 +- test/support/resources/author.ex | 2 +- test/support/resources/bio.ex | 2 +- test/support/resources/chat.ex | 2 +- test/support/resources/co_authored_post.ex | 2 +- test/support/resources/comedian.ex | 2 +- test/support/resources/comment.ex | 2 +- test/support/resources/comment_link.ex | 2 +- test/support/resources/content.ex | 2 +- test/support/resources/content_visibility_group.ex | 2 +- test/support/resources/csv.ex | 2 +- test/support/resources/customer.ex | 2 +- test/support/resources/db_point.ex | 2 +- test/support/resources/db_string_point.ex | 2 +- test/support/resources/entity.ex | 2 +- test/support/resources/integer_post.ex | 2 +- test/support/resources/invite.ex | 2 +- test/support/resources/joke.ex | 2 +- test/support/resources/manager.ex | 2 +- test/support/resources/message.ex | 2 +- test/support/resources/note.ex | 2 +- test/support/resources/order.ex | 2 +- test/support/resources/organization.ex | 2 +- test/support/resources/permalink.ex | 2 +- test/support/resources/post.ex | 2 +- test/support/resources/post_follower.ex | 2 +- test/support/resources/post_link.ex | 2 +- test/support/resources/post_tag.ex | 2 +- test/support/resources/post_views.ex | 2 +- test/support/resources/post_with_empty_update.ex | 2 +- test/support/resources/product.ex | 2 +- test/support/resources/profile.ex | 2 +- test/support/resources/punchline.ex | 2 +- test/support/resources/rating.ex | 2 +- test/support/resources/record.ex | 2 +- test/support/resources/record_temp_entity.ex | 2 +- test/support/resources/role.ex | 2 +- test/support/resources/rsvp.ex | 2 +- test/support/resources/settings.ex | 2 +- test/support/resources/staff_group.ex | 2 +- test/support/resources/staff_group_member.ex | 2 +- test/support/resources/standup_club.ex | 2 +- test/support/resources/stateful_post_follwer.ex | 2 +- test/support/resources/subquery/access.ex | 2 +- test/support/resources/subquery/child.ex | 2 +- test/support/resources/subquery/child_domain.ex | 2 +- test/support/resources/subquery/parent.ex | 2 +- test/support/resources/subquery/parent_domain.ex | 2 +- test/support/resources/subquery/through.ex | 2 +- test/support/resources/tag.ex | 2 +- test/support/resources/temp_entity.ex | 2 +- test/support/resources/user.ex | 2 +- test/support/string_agg.ex | 2 +- test/support/test_app.ex | 2 +- test/support/test_custom_extension.ex | 2 +- test/support/test_no_sandbox_repo.ex | 2 +- test/support/test_repo.ex | 2 +- test/support/trigram_word_similarity.ex | 2 +- test/support/types/composite_point.ex | 2 +- test/support/types/email.ex | 2 +- test/support/types/money.ex | 2 +- test/support/types/person_detail.ex | 2 +- test/support/types/point.ex | 2 +- test/support/types/response.ex | 2 +- test/support/types/status.ex | 2 +- test/support/types/status_enum.ex | 2 +- test/support/types/status_enum_no_cast.ex | 2 +- test/support/types/string_point.ex | 2 +- test/support/unrelated_aggregates/profile.ex | 2 +- test/support/unrelated_aggregates/report.ex | 2 +- test/support/unrelated_aggregates/secure_profile.ex | 2 +- test/support/unrelated_aggregates/user.ex | 2 +- test/test_helper.exs | 2 +- test/transaction_test.exs | 2 +- test/tuple_test.exs | 2 +- test/type_test.exs | 2 +- test/unique_identity_test.exs | 2 +- test/unrelated_aggregates_test.exs | 2 +- test/update_test.exs | 2 +- test/upsert_test.exs | 2 +- 489 files changed, 513 insertions(+), 513 deletions(-) diff --git a/.check.exs b/.check.exs index cecfbc08..25e480f8 100644 --- a/.check.exs +++ b/.check.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/.credo.exs b/.credo.exs index 1e1ebe4c..233f96e7 100644 --- a/.credo.exs +++ b/.credo.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/.formatter.exs b/.formatter.exs index 07bd4f93..71cf7ae6 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 09b96595..16a96790 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index cf4e4897..bec3b86f 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/.gitignore b/.gitignore index b16f7ffb..b7c11866 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/.tool-versions.license b/.tool-versions.license index 815664f3..b0a44fab 100644 --- a/.tool-versions.license +++ b/.tool-versions.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/.vscode/settings.json.license b/.vscode/settings.json.license index 815664f3..b0a44fab 100644 --- a/.vscode/settings.json.license +++ b/.vscode/settings.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/benchmarks/bulk_create.exs b/benchmarks/bulk_create.exs index 5e665f19..c242f236 100644 --- a/benchmarks/bulk_create.exs +++ b/benchmarks/bulk_create.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/config/config.exs b/config/config.exs index 5a179e32..b7f0c0b3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/documentation/dsls/DSL-AshPostgres.DataLayer.md.license b/documentation/dsls/DSL-AshPostgres.DataLayer.md.license index 815664f3..b0a44fab 100644 --- a/documentation/dsls/DSL-AshPostgres.DataLayer.md.license +++ b/documentation/dsls/DSL-AshPostgres.DataLayer.md.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/lib/ash_postgres.ex b/lib/ash_postgres.ex index 34f5cd43..f3c69313 100644 --- a/lib/ash_postgres.ex +++ b/lib/ash_postgres.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/check_constraint.ex b/lib/check_constraint.ex index 6534a464..679ee2a4 100644 --- a/lib/check_constraint.ex +++ b/lib/check_constraint.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/custom_aggregate.ex b/lib/custom_aggregate.ex index 6a42b026..af5936a9 100644 --- a/lib/custom_aggregate.ex +++ b/lib/custom_aggregate.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/custom_extension.ex b/lib/custom_extension.ex index 968a748c..95262e6a 100644 --- a/lib/custom_extension.ex +++ b/lib/custom_extension.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/custom_index.ex b/lib/custom_index.ex index 240b30d2..909dc07d 100644 --- a/lib/custom_index.ex +++ b/lib/custom_index.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 637b21f4..894de3fb 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index 933ca6e8..b36e9f86 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/ecto_migration_default.ex b/lib/ecto_migration_default.ex index d865471b..1e417126 100644 --- a/lib/ecto_migration_default.ex +++ b/lib/ecto_migration_default.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/extensions/immutable_raise_error.ex b/lib/extensions/immutable_raise_error.ex index 22760442..a1a4032c 100644 --- a/lib/extensions/immutable_raise_error.ex +++ b/lib/extensions/immutable_raise_error.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/extensions/vector.ex b/lib/extensions/vector.ex index c029dcf4..ffd281af 100644 --- a/lib/extensions/vector.ex +++ b/lib/extensions/vector.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/functions/binding.ex b/lib/functions/binding.ex index df20a209..9f6a15e7 100644 --- a/lib/functions/binding.ex +++ b/lib/functions/binding.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/functions/ilike.ex b/lib/functions/ilike.ex index dd960f5f..35ee4bf0 100644 --- a/lib/functions/ilike.ex +++ b/lib/functions/ilike.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/functions/like.ex b/lib/functions/like.ex index fdc76c6b..bd160f66 100644 --- a/lib/functions/like.ex +++ b/lib/functions/like.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/functions/trigram_similarity.ex b/lib/functions/trigram_similarity.ex index 19622647..20250d90 100644 --- a/lib/functions/trigram_similarity.ex +++ b/lib/functions/trigram_similarity.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/functions/vector_cosine_distance.ex b/lib/functions/vector_cosine_distance.ex index 1a811583..afbb9b89 100644 --- a/lib/functions/vector_cosine_distance.ex +++ b/lib/functions/vector_cosine_distance.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/functions/vector_l2_distance.ex b/lib/functions/vector_l2_distance.ex index 063a1432..5d1ec62b 100644 --- a/lib/functions/vector_l2_distance.ex +++ b/lib/functions/vector_l2_distance.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/igniter.ex b/lib/igniter.ex index aa20746d..8d9e0808 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/manual_relationship.ex b/lib/manual_relationship.ex index b6322de9..c3c4f030 100644 --- a/lib/manual_relationship.ex +++ b/lib/manual_relationship.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/migration.ex b/lib/migration.ex index 9102d2a9..9ff83c94 100644 --- a/lib/migration.ex +++ b/lib/migration.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/migration_compile_cache.ex b/lib/migration_compile_cache.ex index f421d3b6..5993dffd 100644 --- a/lib/migration_compile_cache.ex +++ b/lib/migration_compile_cache.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/migration_generator/ash_functions.ex b/lib/migration_generator/ash_functions.ex index 1206d829..0ad49bdc 100644 --- a/lib/migration_generator/ash_functions.ex +++ b/lib/migration_generator/ash_functions.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index bdd82fba..6cdbc469 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/migration_generator/operation.ex b/lib/migration_generator/operation.ex index 21af504b..b7b09792 100644 --- a/lib/migration_generator/operation.ex +++ b/lib/migration_generator/operation.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/migration_generator/phase.ex b/lib/migration_generator/phase.ex index 9b13dadd..28e380d9 100644 --- a/lib/migration_generator/phase.ex +++ b/lib/migration_generator/phase.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/helpers.ex b/lib/mix/helpers.ex index 51db8a64..be9b15ea 100644 --- a/lib/mix/helpers.ex +++ b/lib/mix/helpers.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.create.ex b/lib/mix/tasks/ash_postgres.create.ex index 61129c48..392f8d40 100644 --- a/lib/mix/tasks/ash_postgres.create.ex +++ b/lib/mix/tasks/ash_postgres.create.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.drop.ex b/lib/mix/tasks/ash_postgres.drop.ex index 2c50e9b3..9c3c3888 100644 --- a/lib/mix/tasks/ash_postgres.drop.ex +++ b/lib/mix/tasks/ash_postgres.drop.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.gen.resources.ex b/lib/mix/tasks/ash_postgres.gen.resources.ex index d207ea3f..1bd6125c 100644 --- a/lib/mix/tasks/ash_postgres.gen.resources.ex +++ b/lib/mix/tasks/ash_postgres.gen.resources.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.generate_migrations.ex b/lib/mix/tasks/ash_postgres.generate_migrations.ex index ca16404d..41936ce7 100644 --- a/lib/mix/tasks/ash_postgres.generate_migrations.ex +++ b/lib/mix/tasks/ash_postgres.generate_migrations.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.install.ex b/lib/mix/tasks/ash_postgres.install.ex index e7b6e0fb..321ac0e5 100644 --- a/lib/mix/tasks/ash_postgres.install.ex +++ b/lib/mix/tasks/ash_postgres.install.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.migrate.ex b/lib/mix/tasks/ash_postgres.migrate.ex index 601a5daf..9295d7d4 100644 --- a/lib/mix/tasks/ash_postgres.migrate.ex +++ b/lib/mix/tasks/ash_postgres.migrate.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.rollback.ex b/lib/mix/tasks/ash_postgres.rollback.ex index da11f74c..30e39780 100644 --- a/lib/mix/tasks/ash_postgres.rollback.ex +++ b/lib/mix/tasks/ash_postgres.rollback.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.setup_vector.ex b/lib/mix/tasks/ash_postgres.setup_vector.ex index 209c8241..80c12911 100644 --- a/lib/mix/tasks/ash_postgres.setup_vector.ex +++ b/lib/mix/tasks/ash_postgres.setup_vector.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/mix/tasks/ash_postgres.squash_snapshots.ex b/lib/mix/tasks/ash_postgres.squash_snapshots.ex index 8e40a597..8729d870 100644 --- a/lib/mix/tasks/ash_postgres.squash_snapshots.ex +++ b/lib/mix/tasks/ash_postgres.squash_snapshots.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/multitenancy.ex b/lib/multitenancy.ex index 5660e963..56d3f721 100644 --- a/lib/multitenancy.ex +++ b/lib/multitenancy.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/reference.ex b/lib/reference.ex index f22abc1f..68763581 100644 --- a/lib/reference.ex +++ b/lib/reference.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/repo.ex b/lib/repo.ex index fdc2d522..696c2442 100644 --- a/lib/repo.ex +++ b/lib/repo.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/repo/before_compile.ex b/lib/repo/before_compile.ex index c8306507..adb79837 100644 --- a/lib/repo/before_compile.ex +++ b/lib/repo/before_compile.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/resource_generator/resource_generator.ex b/lib/resource_generator/resource_generator.ex index 0f633f7b..b61a0f56 100644 --- a/lib/resource_generator/resource_generator.ex +++ b/lib/resource_generator/resource_generator.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/resource_generator/sensitive_data.ex b/lib/resource_generator/sensitive_data.ex index 30c94857..2adf0c18 100644 --- a/lib/resource_generator/sensitive_data.ex +++ b/lib/resource_generator/sensitive_data.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/resource_generator/spec.ex b/lib/resource_generator/spec.ex index c4d8088e..1a39a960 100644 --- a/lib/resource_generator/spec.ex +++ b/lib/resource_generator/spec.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 84d31935..3aed4340 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/statement.ex b/lib/statement.ex index 5902d730..926d6169 100644 --- a/lib/statement.ex +++ b/lib/statement.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/type.ex b/lib/type.ex index 88c6ef63..d0870002 100644 --- a/lib/type.ex +++ b/lib/type.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/ci_string_wrapper.ex b/lib/types/ci_string_wrapper.ex index 5c7bb8cb..b243f71e 100644 --- a/lib/types/ci_string_wrapper.ex +++ b/lib/types/ci_string_wrapper.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/ltree.ex b/lib/types/ltree.ex index 0997bdff..29d7f9f7 100644 --- a/lib/types/ltree.ex +++ b/lib/types/ltree.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/string_wrapper.ex b/lib/types/string_wrapper.ex index 5d8c33ea..18bedc34 100644 --- a/lib/types/string_wrapper.ex +++ b/lib/types/string_wrapper.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/timestamptz.ex b/lib/types/timestamptz.ex index c684751c..83746e1b 100644 --- a/lib/types/timestamptz.ex +++ b/lib/types/timestamptz.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/timestamptz_usec.ex b/lib/types/timestamptz_usec.ex index 8bd0a38d..6c998a00 100644 --- a/lib/types/timestamptz_usec.ex +++ b/lib/types/timestamptz_usec.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/tsquery.ex b/lib/types/tsquery.ex index 8e1fb7ad..d4a97470 100644 --- a/lib/types/tsquery.ex +++ b/lib/types/tsquery.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/types/tsvector.ex b/lib/types/tsvector.ex index 1a29a359..193a4c0b 100644 --- a/lib/types/tsvector.ex +++ b/lib/types/tsvector.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/verifiers/ensure_table_or_polymorphic.ex b/lib/verifiers/ensure_table_or_polymorphic.ex index 9c1d17bc..a4268f8c 100644 --- a/lib/verifiers/ensure_table_or_polymorphic.ex +++ b/lib/verifiers/ensure_table_or_polymorphic.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex index a595c95c..2ac5e693 100644 --- a/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex +++ b/lib/verifiers/prevent_attribute_multitenancy_and_non_full_match_type.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/verifiers/prevent_multidimensional_array_aggregates.ex b/lib/verifiers/prevent_multidimensional_array_aggregates.ex index 7589b277..f2c76924 100644 --- a/lib/verifiers/prevent_multidimensional_array_aggregates.ex +++ b/lib/verifiers/prevent_multidimensional_array_aggregates.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/verifiers/validate_identity_index_names.ex b/lib/verifiers/validate_identity_index_names.ex index 1c2fbad8..e5cf22c4 100644 --- a/lib/verifiers/validate_identity_index_names.ex +++ b/lib/verifiers/validate_identity_index_names.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/verifiers/validate_references.ex b/lib/verifiers/validate_references.ex index efcffd2d..50be8a30 100644 --- a/lib/verifiers/validate_references.ex +++ b/lib/verifiers/validate_references.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/lib/version_agent.ex b/lib/version_agent.ex index 0db6f4ff..da93d184 100644 --- a/lib/version_agent.ex +++ b/lib/version_agent.ex @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/logos/small-logo.png.license b/logos/small-logo.png.license index 815664f3..b0a44fab 100644 --- a/logos/small-logo.png.license +++ b/logos/small-logo.png.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/mix.exs b/mix.exs index 0bd7385a..2c0f979c 100644 --- a/mix.exs +++ b/mix.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/mix.lock.license b/mix.lock.license index 815664f3..b0a44fab 100644 --- a/mix.lock.license +++ b/mix.lock.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs b/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs index 57fc3e50..1b4d0d71 100644 --- a/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs +++ b/priv/dev_test_repo/migrations/20250526214825_migrate_resources_extensions_1.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs b/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs index bc0f2abf..da8cdb0f 100644 --- a/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs +++ b/priv/dev_test_repo/migrations/20250526214827_migrate_resources1.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/dev_test_repo/extensions.json.license b/priv/resource_snapshots/dev_test_repo/extensions.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/dev_test_repo/extensions.json.license +++ b/priv/resource_snapshots/dev_test_repo/extensions.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license b/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license +++ b/priv/resource_snapshots/dev_test_repo/multitenant_orgs/20250526214827.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license +++ b/priv/resource_snapshots/test_no_sandbox_repo/extensions.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license b/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license +++ b/priv/resource_snapshots/test_repo/accounts/20221217123726.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license b/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/accounts/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20220805191443.json.license b/priv/resource_snapshots/test_repo/authors/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/authors/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/authors/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20220914104733.json.license b/priv/resource_snapshots/test_repo/authors/20220914104733.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/authors/20220914104733.json.license +++ b/priv/resource_snapshots/test_repo/authors/20220914104733.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20240327211150.json.license b/priv/resource_snapshots/test_repo/authors/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/authors/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/authors/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20240705113722.json.license b/priv/resource_snapshots/test_repo/authors/20240705113722.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/authors/20240705113722.json.license +++ b/priv/resource_snapshots/test_repo/authors/20240705113722.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20250908212414.json.license b/priv/resource_snapshots/test_repo/authors/20250908212414.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/authors/20250908212414.json.license +++ b/priv/resource_snapshots/test_repo/authors/20250908212414.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/chats/20250908093505.json.license b/priv/resource_snapshots/test_repo/chats/20250908093505.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/chats/20250908093505.json.license +++ b/priv/resource_snapshots/test_repo/chats/20250908093505.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license b/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license +++ b/priv/resource_snapshots/test_repo/co_authored_posts/20241208221219.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license b/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license +++ b/priv/resource_snapshots/test_repo/comedians/20241217232254.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license b/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license +++ b/priv/resource_snapshots/test_repo/comedians/20250413141328.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license b/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license +++ b/priv/resource_snapshots/test_repo/comment_links/20250123161002.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license b/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/comment_ratings/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license b/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/comment_ratings/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comments/20220805191443.json.license b/priv/resource_snapshots/test_repo/comments/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comments/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/comments/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comments/20240327211150.json.license b/priv/resource_snapshots/test_repo/comments/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comments/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/comments/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/comments/20240327211917.json.license b/priv/resource_snapshots/test_repo/comments/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/comments/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/comments/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20230816231942.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20231116013020.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_certifications_channel_members/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license b/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20231116013020.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_channels/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20230816231942.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_documentations/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license b/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_folder_items/20250714225304.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license b/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_folders/20250714225304.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license b/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_skills/20230816231942.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license b/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/complex_calculations_skills/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/content/20250123164209.json.license b/priv/resource_snapshots/test_repo/content/20250123164209.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/content/20250123164209.json.license +++ b/priv/resource_snapshots/test_repo/content/20250123164209.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license b/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license +++ b/priv/resource_snapshots/test_repo/content_visibility_group/20250123164209.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/csv/20250320225052.json.license b/priv/resource_snapshots/test_repo/csv/20250320225052.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/csv/20250320225052.json.license +++ b/priv/resource_snapshots/test_repo/csv/20250320225052.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/customers/20250908073737.json.license b/priv/resource_snapshots/test_repo/customers/20250908073737.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/customers/20250908073737.json.license +++ b/priv/resource_snapshots/test_repo/customers/20250908073737.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/entities/20240109160153.json.license b/priv/resource_snapshots/test_repo/entities/20240109160153.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/entities/20240109160153.json.license +++ b/priv/resource_snapshots/test_repo/entities/20240109160153.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/entities/20240327211150.json.license b/priv/resource_snapshots/test_repo/entities/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/entities/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/entities/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/entities/20240327211917.json.license b/priv/resource_snapshots/test_repo/entities/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/entities/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/entities/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/extensions.json.license b/priv/resource_snapshots/test_repo/extensions.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/extensions.json.license +++ b/priv/resource_snapshots/test_repo/extensions.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license b/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/integer_posts/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/items/20240713134055.json.license b/priv/resource_snapshots/test_repo/items/20240713134055.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/items/20240713134055.json.license +++ b/priv/resource_snapshots/test_repo/items/20240713134055.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/items/20240717104854.json.license b/priv/resource_snapshots/test_repo/items/20240717104854.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/items/20240717104854.json.license +++ b/priv/resource_snapshots/test_repo/items/20240717104854.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/items/20240717153736.json.license b/priv/resource_snapshots/test_repo/items/20240717153736.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/items/20240717153736.json.license +++ b/priv/resource_snapshots/test_repo/items/20240717153736.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license b/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license +++ b/priv/resource_snapshots/test_repo/jokes/20241217232254.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license b/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license +++ b/priv/resource_snapshots/test_repo/jokes/20250413141328.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/managers/20230526144249.json.license b/priv/resource_snapshots/test_repo/managers/20230526144249.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/managers/20230526144249.json.license +++ b/priv/resource_snapshots/test_repo/managers/20230526144249.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/managers/20240327211150.json.license b/priv/resource_snapshots/test_repo/managers/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/managers/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/managers/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/messages/20250908093505.json.license b/priv/resource_snapshots/test_repo/messages/20250908093505.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/messages/20250908093505.json.license +++ b/priv/resource_snapshots/test_repo/messages/20250908093505.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license b/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license +++ b/priv/resource_snapshots/test_repo/multitenant_named_orgs/20250519103535.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240627223225.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240702164513.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license b/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license +++ b/priv/resource_snapshots/test_repo/multitenant_orgs/20240703155134.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license b/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license +++ b/priv/resource_snapshots/test_repo/non_multitenant_post_links/20250122190558.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/note/20250123164209.json.license b/priv/resource_snapshots/test_repo/note/20250123164209.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/note/20250123164209.json.license +++ b/priv/resource_snapshots/test_repo/note/20250123164209.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orders/20250908073737.json.license b/priv/resource_snapshots/test_repo/orders/20250908073737.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/orders/20250908073737.json.license +++ b/priv/resource_snapshots/test_repo/orders/20250908073737.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license b/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license +++ b/priv/resource_snapshots/test_repo/orgs/20230129050950.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license b/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/orgs/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license b/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license +++ b/priv/resource_snapshots/test_repo/orgs/20250210191116.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license b/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license +++ b/priv/resource_snapshots/test_repo/other_items/20240713134055.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license b/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license +++ b/priv/resource_snapshots/test_repo/other_items/20240717151815.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/points/20250313112823.json.license b/priv/resource_snapshots/test_repo/points/20250313112823.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/points/20250313112823.json.license +++ b/priv/resource_snapshots/test_repo/points/20250313112823.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license b/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license +++ b/priv/resource_snapshots/test_repo/post_followers/20240227180858.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license b/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license +++ b/priv/resource_snapshots/test_repo/post_followers/20240227181137.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license b/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/post_followers/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license b/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license +++ b/priv/resource_snapshots/test_repo/post_followers/20240516205244.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license b/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license +++ b/priv/resource_snapshots/test_repo/post_followers/20240517223946.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license b/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/post_links/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license b/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license +++ b/priv/resource_snapshots/test_repo/post_links/20221017133955.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license b/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license +++ b/priv/resource_snapshots/test_repo/post_links/20221202194704.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license b/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license +++ b/priv/resource_snapshots/test_repo/post_links/20240610195853.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license b/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license +++ b/priv/resource_snapshots/test_repo/post_links/20240617193218.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license b/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license +++ b/priv/resource_snapshots/test_repo/post_permalinks/20240906170759.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license b/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/post_ratings/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license b/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/post_ratings/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license b/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license +++ b/priv/resource_snapshots/test_repo/post_tags/20250810102512.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license b/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license +++ b/priv/resource_snapshots/test_repo/post_views/20230905050351.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license b/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/post_views/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20220805191443.json.license b/priv/resource_snapshots/test_repo/posts/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/posts/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20221125171150.json.license b/priv/resource_snapshots/test_repo/posts/20221125171150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20221125171150.json.license +++ b/priv/resource_snapshots/test_repo/posts/20221125171150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20221125171204.json.license b/priv/resource_snapshots/test_repo/posts/20221125171204.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20221125171204.json.license +++ b/priv/resource_snapshots/test_repo/posts/20221125171204.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20230129050950.json.license b/priv/resource_snapshots/test_repo/posts/20230129050950.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20230129050950.json.license +++ b/priv/resource_snapshots/test_repo/posts/20230129050950.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20230823161017.json.license b/priv/resource_snapshots/test_repo/posts/20230823161017.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20230823161017.json.license +++ b/priv/resource_snapshots/test_repo/posts/20230823161017.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20231127215636.json.license b/priv/resource_snapshots/test_repo/posts/20231127215636.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20231127215636.json.license +++ b/priv/resource_snapshots/test_repo/posts/20231127215636.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20231129141453.json.license b/priv/resource_snapshots/test_repo/posts/20231129141453.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20231129141453.json.license +++ b/priv/resource_snapshots/test_repo/posts/20231129141453.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20231219132807.json.license b/priv/resource_snapshots/test_repo/posts/20231219132807.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20231219132807.json.license +++ b/priv/resource_snapshots/test_repo/posts/20231219132807.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240129221511.json.license b/priv/resource_snapshots/test_repo/posts/20240129221511.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240129221511.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240129221511.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240224001913.json.license b/priv/resource_snapshots/test_repo/posts/20240224001913.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240224001913.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240224001913.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240327211150.json.license b/priv/resource_snapshots/test_repo/posts/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240327211917.json.license b/priv/resource_snapshots/test_repo/posts/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240503012410.json.license b/priv/resource_snapshots/test_repo/posts/20240503012410.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240503012410.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240503012410.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240504185511.json.license b/priv/resource_snapshots/test_repo/posts/20240504185511.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240504185511.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240504185511.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240524031113.json.license b/priv/resource_snapshots/test_repo/posts/20240524031113.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240524031113.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240524031113.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240524041750.json.license b/priv/resource_snapshots/test_repo/posts/20240524041750.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240524041750.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240524041750.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240617193218.json.license b/priv/resource_snapshots/test_repo/posts/20240617193218.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240617193218.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240617193218.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240618102809.json.license b/priv/resource_snapshots/test_repo/posts/20240618102809.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240618102809.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240618102809.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240712232026.json.license b/priv/resource_snapshots/test_repo/posts/20240712232026.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240712232026.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240712232026.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240715135403.json.license b/priv/resource_snapshots/test_repo/posts/20240715135403.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240715135403.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240715135403.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240910180107.json.license b/priv/resource_snapshots/test_repo/posts/20240910180107.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240910180107.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240910180107.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240911225320.json.license b/priv/resource_snapshots/test_repo/posts/20240911225320.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240911225320.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240911225320.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240918104740.json.license b/priv/resource_snapshots/test_repo/posts/20240918104740.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240918104740.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240918104740.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20240929121224.json.license b/priv/resource_snapshots/test_repo/posts/20240929121224.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20240929121224.json.license +++ b/priv/resource_snapshots/test_repo/posts/20240929121224.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250217054207.json.license b/priv/resource_snapshots/test_repo/posts/20250217054207.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20250217054207.json.license +++ b/priv/resource_snapshots/test_repo/posts/20250217054207.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250313112823.json.license b/priv/resource_snapshots/test_repo/posts/20250313112823.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20250313112823.json.license +++ b/priv/resource_snapshots/test_repo/posts/20250313112823.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250520130634.json.license b/priv/resource_snapshots/test_repo/posts/20250520130634.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20250520130634.json.license +++ b/priv/resource_snapshots/test_repo/posts/20250520130634.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250521105654.json.license b/priv/resource_snapshots/test_repo/posts/20250521105654.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20250521105654.json.license +++ b/priv/resource_snapshots/test_repo/posts/20250521105654.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250612113920.json.license b/priv/resource_snapshots/test_repo/posts/20250612113920.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20250612113920.json.license +++ b/priv/resource_snapshots/test_repo/posts/20250612113920.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/posts/20250618011917.json.license b/priv/resource_snapshots/test_repo/posts/20250618011917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/posts/20250618011917.json.license +++ b/priv/resource_snapshots/test_repo/posts/20250618011917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/products/20250908073737.json.license b/priv/resource_snapshots/test_repo/products/20250908073737.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/products/20250908073737.json.license +++ b/priv/resource_snapshots/test_repo/products/20250908073737.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/profile/20220805191443.json.license b/priv/resource_snapshots/test_repo/profile/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/profile/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/profile/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license b/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/profiles.profile/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license b/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license +++ b/priv/resource_snapshots/test_repo/punchlines/20250413141328.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records/20240109160153.json.license b/priv/resource_snapshots/test_repo/records/20240109160153.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/records/20240109160153.json.license +++ b/priv/resource_snapshots/test_repo/records/20240109160153.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records/20240327211150.json.license b/priv/resource_snapshots/test_repo/records/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/records/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/records/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records/20240327211917.json.license b/priv/resource_snapshots/test_repo/records/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/records/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/records/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license b/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license +++ b/priv/resource_snapshots/test_repo/records_temp_entities/20250605230457.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license b/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license +++ b/priv/resource_snapshots/test_repo/relationship_items/20240717153736.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license b/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license +++ b/priv/resource_snapshots/test_repo/rsvps/20251002180954.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license b/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license +++ b/priv/resource_snapshots/test_repo/schematic_groups/20240821213522.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license b/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license +++ b/priv/resource_snapshots/test_repo/staff_group/20250123164209.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license b/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license +++ b/priv/resource_snapshots/test_repo/staff_group_member/20250123164209.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license b/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license +++ b/priv/resource_snapshots/test_repo/standup_clubs/20250413141328.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license b/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license +++ b/priv/resource_snapshots/test_repo/stateful_post_followers/20240618085942.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license b/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license +++ b/priv/resource_snapshots/test_repo/string_points/20250313112823.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license b/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license +++ b/priv/resource_snapshots/test_repo/sub_items/20240713134055.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license +++ b/priv/resource_snapshots/test_repo/subquery_access/20240130133933.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license +++ b/priv/resource_snapshots/test_repo/subquery_child/20240130133933.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license +++ b/priv/resource_snapshots/test_repo/subquery_parent/20240130133933.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license b/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license +++ b/priv/resource_snapshots/test_repo/subquery_through/20240130133933.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tags/20250810102512.json.license b/priv/resource_snapshots/test_repo/tags/20250810102512.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tags/20250810102512.json.license +++ b/priv/resource_snapshots/test_repo/tags/20250810102512.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license +++ b/priv/resource_snapshots/test_repo/temp.temp_entities/20240327211917.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license b/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license +++ b/priv/resource_snapshots/test_repo/temp_entities/20240109160153.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license +++ b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073135.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license +++ b/priv/resource_snapshots/test_repo/tenants/composite_key/20250220073141.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license b/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license +++ b/priv/resource_snapshots/test_repo/tenants/cross_tenant_post_links/20250122203454.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license b/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license +++ b/priv/resource_snapshots/test_repo/tenants/friend_links/20240610162043.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license +++ b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20220805191441.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license +++ b/priv/resource_snapshots/test_repo/tenants/multitenant_posts/20240327211149.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license +++ b/priv/resource_snapshots/test_repo/tenants/non_multitenant_post_multitenant_links/20251001120813.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license +++ b/priv/resource_snapshots/test_repo/unrelated_profiles/20250731124648.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license +++ b/priv/resource_snapshots/test_repo/unrelated_reports/20250731124648.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license +++ b/priv/resource_snapshots/test_repo/unrelated_secure_profiles/20250731124648.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license b/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license +++ b/priv/resource_snapshots/test_repo/unrelated_users/20250731124648.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license b/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license +++ b/priv/resource_snapshots/test_repo/user_invites/20240727145758.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20220805191443.json.license b/priv/resource_snapshots/test_repo/users/20220805191443.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20220805191443.json.license +++ b/priv/resource_snapshots/test_repo/users/20220805191443.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20221217123726.json.license b/priv/resource_snapshots/test_repo/users/20221217123726.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20221217123726.json.license +++ b/priv/resource_snapshots/test_repo/users/20221217123726.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20230129050950.json.license b/priv/resource_snapshots/test_repo/users/20230129050950.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20230129050950.json.license +++ b/priv/resource_snapshots/test_repo/users/20230129050950.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20240327211150.json.license b/priv/resource_snapshots/test_repo/users/20240327211150.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20240327211150.json.license +++ b/priv/resource_snapshots/test_repo/users/20240327211150.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20240727145758.json.license b/priv/resource_snapshots/test_repo/users/20240727145758.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20240727145758.json.license +++ b/priv/resource_snapshots/test_repo/users/20240727145758.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20240929124728.json.license b/priv/resource_snapshots/test_repo/users/20240929124728.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20240929124728.json.license +++ b/priv/resource_snapshots/test_repo/users/20240929124728.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20250320225052.json.license b/priv/resource_snapshots/test_repo/users/20250320225052.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20250320225052.json.license +++ b/priv/resource_snapshots/test_repo/users/20250320225052.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/users/20250321142835.json.license b/priv/resource_snapshots/test_repo/users/20250321142835.json.license index 815664f3..b0a44fab 100644 --- a/priv/resource_snapshots/test_repo/users/20250321142835.json.license +++ b/priv/resource_snapshots/test_repo/users/20250321142835.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/test_no_sandbox_repo/migrations/.gitkeep.license b/priv/test_no_sandbox_repo/migrations/.gitkeep.license index 815664f3..b0a44fab 100644 --- a/priv/test_no_sandbox_repo/migrations/.gitkeep.license +++ b/priv/test_no_sandbox_repo/migrations/.gitkeep.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2020 Zach Daniel +SPDX-FileCopyrightText: 2019 ash_postgres contributors SPDX-License-Identifier: MIT diff --git a/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs b/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs index 94e450c6..f64c49e7 100644 --- a/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs +++ b/priv/test_no_sandbox_repo/migrations/20240627223224_install_5_extensions.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs b/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs index ecf0a75f..3b0c3d9d 100644 --- a/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs +++ b/priv/test_no_sandbox_repo/migrations/20240712232025_install_ash-functions_extension_4.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs b/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs index 01697a60..8ab2eb47 100644 --- a/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs +++ b/priv/test_no_sandbox_repo/migrations/20250113205301_migrate_resources_extensions_1.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20220805191440_install_4_extensions.exs b/priv/test_repo/migrations/20220805191440_install_4_extensions.exs index a20aa8e9..a1d465dc 100644 --- a/priv/test_repo/migrations/20220805191440_install_4_extensions.exs +++ b/priv/test_repo/migrations/20220805191440_install_4_extensions.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -88,4 +88,4 @@ defmodule AshPostgres.TestRepo.Migrations.Install4Extensions do # execute("DROP EXTENSION IF EXISTS \"pg_trgm\"") # execute("DROP EXTENSION IF EXISTS \"citext\"") end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20220805191443_migrate_resources1.exs b/priv/test_repo/migrations/20220805191443_migrate_resources1.exs index b9b7fdd0..8cb31e0c 100644 --- a/priv/test_repo/migrations/20220805191443_migrate_resources1.exs +++ b/priv/test_repo/migrations/20220805191443_migrate_resources1.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20220914104733_migrate_resources2.exs b/priv/test_repo/migrations/20220914104733_migrate_resources2.exs index fbbeb8e0..b7da4e0d 100644 --- a/priv/test_repo/migrations/20220914104733_migrate_resources2.exs +++ b/priv/test_repo/migrations/20220914104733_migrate_resources2.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources2 do remove :badges end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20221017133955_migrate_resources3.exs b/priv/test_repo/migrations/20221017133955_migrate_resources3.exs index 12f282d6..f0593998 100644 --- a/priv/test_repo/migrations/20221017133955_migrate_resources3.exs +++ b/priv/test_repo/migrations/20221017133955_migrate_resources3.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources3 do name: "post_links_unique_link_index" ) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20221125171148_migrate_resources4.exs b/priv/test_repo/migrations/20221125171148_migrate_resources4.exs index b6749230..9e65c9a7 100644 --- a/priv/test_repo/migrations/20221125171148_migrate_resources4.exs +++ b/priv/test_repo/migrations/20221125171148_migrate_resources4.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -24,4 +24,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources4 do remove :uniq_custom_one end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20221125171150_migrate_resources5.exs b/priv/test_repo/migrations/20221125171150_migrate_resources5.exs index f4d14b56..acccc8af 100644 --- a/priv/test_repo/migrations/20221125171150_migrate_resources5.exs +++ b/priv/test_repo/migrations/20221125171150_migrate_resources5.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -23,4 +23,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources5 do name: "posts_uniq_custom_one_uniq_custom_two_index" ) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20221202194704_migrate_resources6.exs b/priv/test_repo/migrations/20221202194704_migrate_resources6.exs index 6b6f174a..57200540 100644 --- a/priv/test_repo/migrations/20221202194704_migrate_resources6.exs +++ b/priv/test_repo/migrations/20221202194704_migrate_resources6.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources6 do remove :state end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20221217123726_migrate_resources7.exs b/priv/test_repo/migrations/20221217123726_migrate_resources7.exs index 95edab52..2793bd5c 100644 --- a/priv/test_repo/migrations/20221217123726_migrate_resources7.exs +++ b/priv/test_repo/migrations/20221217123726_migrate_resources7.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -39,4 +39,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources7 do remove :is_active end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230129050950_migrate_resources8.exs b/priv/test_repo/migrations/20230129050950_migrate_resources8.exs index 3aba9340..30a4ab95 100644 --- a/priv/test_repo/migrations/20230129050950_migrate_resources8.exs +++ b/priv/test_repo/migrations/20230129050950_migrate_resources8.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -76,4 +76,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources8 do remove :organization_id end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230526144249_migrate_resources9.exs b/priv/test_repo/migrations/20230526144249_migrate_resources9.exs index d3a907b8..1ff50786 100644 --- a/priv/test_repo/migrations/20230526144249_migrate_resources9.exs +++ b/priv/test_repo/migrations/20230526144249_migrate_resources9.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -38,4 +38,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources9 do drop table(:managers) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs b/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs index 6f9ee9e4..9aad4684 100644 --- a/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs +++ b/priv/test_repo/migrations/20230712182523_install_ash-functions_extension.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs b/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs index edfac408..79f9f0d0 100644 --- a/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs +++ b/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -27,4 +27,4 @@ defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV0 do DROP FUNCTION IF EXISTS ash_demo_functions() """) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs b/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs index 2222a781..5b88cec1 100644 --- a/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs +++ b/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -27,4 +27,4 @@ defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV1 do DROP FUNCTION IF EXISTS ash_demo_functions() """) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs b/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs index 1cc444b7..4070e6d3 100644 --- a/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs +++ b/priv/test_repo/migrations/20230816231942_add_complex_calculation_tables.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -70,4 +70,4 @@ defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationTables do drop table(:complex_calculations_skills) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230823161017_migrate_resources10.exs b/priv/test_repo/migrations/20230823161017_migrate_resources10.exs index 18e6f479..6cf19472 100644 --- a/priv/test_repo/migrations/20230823161017_migrate_resources10.exs +++ b/priv/test_repo/migrations/20230823161017_migrate_resources10.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources10 do remove :stuff end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20230905050351_add_post_views.exs b/priv/test_repo/migrations/20230905050351_add_post_views.exs index 3ea0cbb1..e19c1d62 100644 --- a/priv/test_repo/migrations/20230905050351_add_post_views.exs +++ b/priv/test_repo/migrations/20230905050351_add_post_views.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.AddPostViews do def down do drop table(:post_views) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs b/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs index c3f768f0..29045f0f 100644 --- a/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs +++ b/priv/test_repo/migrations/20231116013020_add_complex_calculations_channels.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -56,4 +56,4 @@ defmodule AshPostgres.TestRepo.Migrations.AddComplexCalculationsChannels do drop table(:complex_calculations_channels) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20231127212608_add_composite_type.exs b/priv/test_repo/migrations/20231127212608_add_composite_type.exs index 8a85a46f..1ae28125 100644 --- a/priv/test_repo/migrations/20231127212608_add_composite_type.exs +++ b/priv/test_repo/migrations/20231127212608_add_composite_type.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20231127215636_migrate_resources11.exs b/priv/test_repo/migrations/20231127215636_migrate_resources11.exs index c4da8e77..97ce50c1 100644 --- a/priv/test_repo/migrations/20231127215636_migrate_resources11.exs +++ b/priv/test_repo/migrations/20231127215636_migrate_resources11.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20231129141453_migrate_resources12.exs b/priv/test_repo/migrations/20231129141453_migrate_resources12.exs index 613a6b42..0063cd3e 100644 --- a/priv/test_repo/migrations/20231129141453_migrate_resources12.exs +++ b/priv/test_repo/migrations/20231129141453_migrate_resources12.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources12 do modify :composite_point, :point end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs index e02f5467..8b3a89f4 100644 --- a/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs +++ b/priv/test_repo/migrations/20231214220937_install_ash-functions_extension_2.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20231219132807_migrate_resources13.exs b/priv/test_repo/migrations/20231219132807_migrate_resources13.exs index 55fc4bbe..b73022f0 100644 --- a/priv/test_repo/migrations/20231219132807_migrate_resources13.exs +++ b/priv/test_repo/migrations/20231219132807_migrate_resources13.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -18,4 +18,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources13 do def down do rename table(:posts), :title_column, to: :title end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs b/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs index e841dfba..75e5d807 100644 --- a/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs +++ b/priv/test_repo/migrations/20231231051611_install_ash-functions_extension_3.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -64,4 +64,4 @@ defmodule AshPostgres.TestRepo.Migrations.InstallAshFunctionsExtension3 do $$ LANGUAGE plpgsql; """) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20240109155951_create_temp_schema.exs b/priv/test_repo/migrations/20240109155951_create_temp_schema.exs index ada02c90..c528b9d0 100644 --- a/priv/test_repo/migrations/20240109155951_create_temp_schema.exs +++ b/priv/test_repo/migrations/20240109155951_create_temp_schema.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240109160153_migrate_resources14.exs b/priv/test_repo/migrations/20240109160153_migrate_resources14.exs index c70f9357..de65bcfa 100644 --- a/priv/test_repo/migrations/20240109160153_migrate_resources14.exs +++ b/priv/test_repo/migrations/20240109160153_migrate_resources14.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -41,4 +41,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources14 do drop table(:temp_entities, prefix: "temp") end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20240129221511_migrate_resources15.exs b/priv/test_repo/migrations/20240129221511_migrate_resources15.exs index 88ae8d08..6f8f055a 100644 --- a/priv/test_repo/migrations/20240129221511_migrate_resources15.exs +++ b/priv/test_repo/migrations/20240129221511_migrate_resources15.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources15 do remove :list_containing_nils end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs b/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs index 36ad998e..4d45ac33 100644 --- a/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs +++ b/priv/test_repo/migrations/20240130133933_add_resources_for_subquery_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240224001913_migrate_resources16.exs b/priv/test_repo/migrations/20240224001913_migrate_resources16.exs index 00decc17..f3b7a8ed 100644 --- a/priv/test_repo/migrations/20240224001913_migrate_resources16.exs +++ b/priv/test_repo/migrations/20240224001913_migrate_resources16.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -22,4 +22,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources16 do remove :list_of_stuff end end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20240227180858_migrate_resources17.exs b/priv/test_repo/migrations/20240227180858_migrate_resources17.exs index 93fc8bd5..f59df09c 100644 --- a/priv/test_repo/migrations/20240227180858_migrate_resources17.exs +++ b/priv/test_repo/migrations/20240227180858_migrate_resources17.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -42,4 +42,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources17 do drop table(:post_followers) end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20240227181137_migrate_resources18.exs b/priv/test_repo/migrations/20240227181137_migrate_resources18.exs index 4e6db225..7e831d9e 100644 --- a/priv/test_repo/migrations/20240227181137_migrate_resources18.exs +++ b/priv/test_repo/migrations/20240227181137_migrate_resources18.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -46,4 +46,4 @@ defmodule AshPostgres.TestRepo.Migrations.MigrateResources18 do rename table(:post_followers), :follower_id, to: :user_id end -end \ No newline at end of file +end diff --git a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs index bdecc817..4bb4f7fa 100644 --- a/priv/test_repo/migrations/20240229050455_install_5_extensions.exs +++ b/priv/test_repo/migrations/20240229050455_install_5_extensions.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240327211150_migrate_resources19.exs b/priv/test_repo/migrations/20240327211150_migrate_resources19.exs index 7fc2c8c3..100d11a9 100644 --- a/priv/test_repo/migrations/20240327211150_migrate_resources19.exs +++ b/priv/test_repo/migrations/20240327211150_migrate_resources19.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240327211917_migrate_resources20.exs b/priv/test_repo/migrations/20240327211917_migrate_resources20.exs index bd30cc5b..8358e319 100644 --- a/priv/test_repo/migrations/20240327211917_migrate_resources20.exs +++ b/priv/test_repo/migrations/20240327211917_migrate_resources20.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240503012410_migrate_resources21.exs b/priv/test_repo/migrations/20240503012410_migrate_resources21.exs index 9230a78a..7f66c108 100644 --- a/priv/test_repo/migrations/20240503012410_migrate_resources21.exs +++ b/priv/test_repo/migrations/20240503012410_migrate_resources21.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240504185511_migrate_resources22.exs b/priv/test_repo/migrations/20240504185511_migrate_resources22.exs index 19ae6c87..ef35bc07 100644 --- a/priv/test_repo/migrations/20240504185511_migrate_resources22.exs +++ b/priv/test_repo/migrations/20240504185511_migrate_resources22.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240516205244_migrate_resources23.exs b/priv/test_repo/migrations/20240516205244_migrate_resources23.exs index cebacd08..1ee5dfad 100644 --- a/priv/test_repo/migrations/20240516205244_migrate_resources23.exs +++ b/priv/test_repo/migrations/20240516205244_migrate_resources23.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240517223946_migrate_resources24.exs b/priv/test_repo/migrations/20240517223946_migrate_resources24.exs index 09e71bd5..e7f09782 100644 --- a/priv/test_repo/migrations/20240517223946_migrate_resources24.exs +++ b/priv/test_repo/migrations/20240517223946_migrate_resources24.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240524031113_migrate_resources25.exs b/priv/test_repo/migrations/20240524031113_migrate_resources25.exs index 0b829659..38e5384f 100644 --- a/priv/test_repo/migrations/20240524031113_migrate_resources25.exs +++ b/priv/test_repo/migrations/20240524031113_migrate_resources25.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240524041750_migrate_resources26.exs b/priv/test_repo/migrations/20240524041750_migrate_resources26.exs index e27aff26..255bf9c9 100644 --- a/priv/test_repo/migrations/20240524041750_migrate_resources26.exs +++ b/priv/test_repo/migrations/20240524041750_migrate_resources26.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240610195853_migrate_resources27.exs b/priv/test_repo/migrations/20240610195853_migrate_resources27.exs index f156231e..e22c4de7 100644 --- a/priv/test_repo/migrations/20240610195853_migrate_resources27.exs +++ b/priv/test_repo/migrations/20240610195853_migrate_resources27.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240617193218_migrate_resources28.exs b/priv/test_repo/migrations/20240617193218_migrate_resources28.exs index d864e25b..019a5dbe 100644 --- a/priv/test_repo/migrations/20240617193218_migrate_resources28.exs +++ b/priv/test_repo/migrations/20240617193218_migrate_resources28.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240618085942_migrate_resources29.exs b/priv/test_repo/migrations/20240618085942_migrate_resources29.exs index 0c6b8de3..9c8fbf81 100644 --- a/priv/test_repo/migrations/20240618085942_migrate_resources29.exs +++ b/priv/test_repo/migrations/20240618085942_migrate_resources29.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240618102809_migrate_resources30.exs b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs index 4d1f8ad8..37399ecc 100644 --- a/priv/test_repo/migrations/20240618102809_migrate_resources30.exs +++ b/priv/test_repo/migrations/20240618102809_migrate_resources30.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs b/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs index 3a544490..d4ce735f 100644 --- a/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs +++ b/priv/test_repo/migrations/20240622192715_install_ash-functions_extension_4.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240627223225_migrate_resources31.exs b/priv/test_repo/migrations/20240627223225_migrate_resources31.exs index cd97c368..7b9f813b 100644 --- a/priv/test_repo/migrations/20240627223225_migrate_resources31.exs +++ b/priv/test_repo/migrations/20240627223225_migrate_resources31.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240703155134_migrate_resources32.exs b/priv/test_repo/migrations/20240703155134_migrate_resources32.exs index 1364a828..1895ad19 100644 --- a/priv/test_repo/migrations/20240703155134_migrate_resources32.exs +++ b/priv/test_repo/migrations/20240703155134_migrate_resources32.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240705113722_migrate_resources33.exs b/priv/test_repo/migrations/20240705113722_migrate_resources33.exs index e6f7a1a8..adf3fa29 100644 --- a/priv/test_repo/migrations/20240705113722_migrate_resources33.exs +++ b/priv/test_repo/migrations/20240705113722_migrate_resources33.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240712232026_migrate_resources34.exs b/priv/test_repo/migrations/20240712232026_migrate_resources34.exs index 22af731f..da51100e 100644 --- a/priv/test_repo/migrations/20240712232026_migrate_resources34.exs +++ b/priv/test_repo/migrations/20240712232026_migrate_resources34.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs b/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs index d6c57db6..60a98ad1 100644 --- a/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs +++ b/priv/test_repo/migrations/20240713134055_multi_domain_calculations.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240715135403_migrate_resources35.exs b/priv/test_repo/migrations/20240715135403_migrate_resources35.exs index 2349c59d..a818451a 100644 --- a/priv/test_repo/migrations/20240715135403_migrate_resources35.exs +++ b/priv/test_repo/migrations/20240715135403_migrate_resources35.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs b/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs index 2c9269fa..7d0b19b7 100644 --- a/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs +++ b/priv/test_repo/migrations/20240717104854_no_attributes_calculation_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240717151815_migrate_resources36.exs b/priv/test_repo/migrations/20240717151815_migrate_resources36.exs index e8b21183..df6b0087 100644 --- a/priv/test_repo/migrations/20240717151815_migrate_resources36.exs +++ b/priv/test_repo/migrations/20240717151815_migrate_resources36.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240717153736_migrate_resources37.exs b/priv/test_repo/migrations/20240717153736_migrate_resources37.exs index 4d5777a4..8e14b6ce 100644 --- a/priv/test_repo/migrations/20240717153736_migrate_resources37.exs +++ b/priv/test_repo/migrations/20240717153736_migrate_resources37.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240727145758_user_invites.exs b/priv/test_repo/migrations/20240727145758_user_invites.exs index c054a440..61c522bc 100644 --- a/priv/test_repo/migrations/20240727145758_user_invites.exs +++ b/priv/test_repo/migrations/20240727145758_user_invites.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240906170759_migrate_resources38.exs b/priv/test_repo/migrations/20240906170759_migrate_resources38.exs index 78637f88..7a7ab279 100644 --- a/priv/test_repo/migrations/20240906170759_migrate_resources38.exs +++ b/priv/test_repo/migrations/20240906170759_migrate_resources38.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240910180107_migrate_resources39.exs b/priv/test_repo/migrations/20240910180107_migrate_resources39.exs index dc7f6502..da7e7728 100644 --- a/priv/test_repo/migrations/20240910180107_migrate_resources39.exs +++ b/priv/test_repo/migrations/20240910180107_migrate_resources39.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240911225319_install_1_extensions.exs b/priv/test_repo/migrations/20240911225319_install_1_extensions.exs index dede4e89..2bac3ea7 100644 --- a/priv/test_repo/migrations/20240911225319_install_1_extensions.exs +++ b/priv/test_repo/migrations/20240911225319_install_1_extensions.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240911225320_migrate_resources40.exs b/priv/test_repo/migrations/20240911225320_migrate_resources40.exs index fd10a2f0..95f01067 100644 --- a/priv/test_repo/migrations/20240911225320_migrate_resources40.exs +++ b/priv/test_repo/migrations/20240911225320_migrate_resources40.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240918104740_migrate_resources41.exs b/priv/test_repo/migrations/20240918104740_migrate_resources41.exs index 41ce709a..3d604b97 100644 --- a/priv/test_repo/migrations/20240918104740_migrate_resources41.exs +++ b/priv/test_repo/migrations/20240918104740_migrate_resources41.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240929121224_migrate_resources42.exs b/priv/test_repo/migrations/20240929121224_migrate_resources42.exs index 23a14e4a..91c49fb4 100644 --- a/priv/test_repo/migrations/20240929121224_migrate_resources42.exs +++ b/priv/test_repo/migrations/20240929121224_migrate_resources42.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20240929124728_migrate_resources43.exs b/priv/test_repo/migrations/20240929124728_migrate_resources43.exs index c91d401a..86b28970 100644 --- a/priv/test_repo/migrations/20240929124728_migrate_resources43.exs +++ b/priv/test_repo/migrations/20240929124728_migrate_resources43.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20241208221219_migrate_resources44.exs b/priv/test_repo/migrations/20241208221219_migrate_resources44.exs index af546799..35a52b61 100644 --- a/priv/test_repo/migrations/20241208221219_migrate_resources44.exs +++ b/priv/test_repo/migrations/20241208221219_migrate_resources44.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20241217232254_migrate_resources45.exs b/priv/test_repo/migrations/20241217232254_migrate_resources45.exs index 94454ef2..c44417ca 100644 --- a/priv/test_repo/migrations/20241217232254_migrate_resources45.exs +++ b/priv/test_repo/migrations/20241217232254_migrate_resources45.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs b/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs index 68c52ef7..842097ce 100644 --- a/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs +++ b/priv/test_repo/migrations/20250113205259_migrate_resources_extensions_1.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250122190558_migrate_resources46.exs b/priv/test_repo/migrations/20250122190558_migrate_resources46.exs index c9cfed58..a4455b1b 100644 --- a/priv/test_repo/migrations/20250122190558_migrate_resources46.exs +++ b/priv/test_repo/migrations/20250122190558_migrate_resources46.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250123161002_migrate_resources47.exs b/priv/test_repo/migrations/20250123161002_migrate_resources47.exs index 32e5ff54..582a215e 100644 --- a/priv/test_repo/migrations/20250123161002_migrate_resources47.exs +++ b/priv/test_repo/migrations/20250123161002_migrate_resources47.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250123164209_migrate_resources48.exs b/priv/test_repo/migrations/20250123164209_migrate_resources48.exs index b07394fb..8f4eccef 100644 --- a/priv/test_repo/migrations/20250123164209_migrate_resources48.exs +++ b/priv/test_repo/migrations/20250123164209_migrate_resources48.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250210191116_migrate_resources49.exs b/priv/test_repo/migrations/20250210191116_migrate_resources49.exs index f147420b..1df9270e 100644 --- a/priv/test_repo/migrations/20250210191116_migrate_resources49.exs +++ b/priv/test_repo/migrations/20250210191116_migrate_resources49.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250217054207_migrate_resources50.exs b/priv/test_repo/migrations/20250217054207_migrate_resources50.exs index 1ea35a4a..7d077e11 100644 --- a/priv/test_repo/migrations/20250217054207_migrate_resources50.exs +++ b/priv/test_repo/migrations/20250217054207_migrate_resources50.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250313112823_migrate_resources51.exs b/priv/test_repo/migrations/20250313112823_migrate_resources51.exs index 37609641..98fd8624 100644 --- a/priv/test_repo/migrations/20250313112823_migrate_resources51.exs +++ b/priv/test_repo/migrations/20250313112823_migrate_resources51.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250320225052_add_csv_resource.exs b/priv/test_repo/migrations/20250320225052_add_csv_resource.exs index 11159d77..e1d72242 100644 --- a/priv/test_repo/migrations/20250320225052_add_csv_resource.exs +++ b/priv/test_repo/migrations/20250320225052_add_csv_resource.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250321142835_migrate_resources52.exs b/priv/test_repo/migrations/20250321142835_migrate_resources52.exs index 68ee928c..7856790b 100644 --- a/priv/test_repo/migrations/20250321142835_migrate_resources52.exs +++ b/priv/test_repo/migrations/20250321142835_migrate_resources52.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs b/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs index ff4a368c..638be99f 100644 --- a/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs +++ b/priv/test_repo/migrations/20250413141328_add_punchlines_and_standup_clubs.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250519103535_migrate_resources53.exs b/priv/test_repo/migrations/20250519103535_migrate_resources53.exs index cabe0ee7..3f56a6cb 100644 --- a/priv/test_repo/migrations/20250519103535_migrate_resources53.exs +++ b/priv/test_repo/migrations/20250519103535_migrate_resources53.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250520130634_migrate_resources54.exs b/priv/test_repo/migrations/20250520130634_migrate_resources54.exs index 063d79b9..e70723cd 100644 --- a/priv/test_repo/migrations/20250520130634_migrate_resources54.exs +++ b/priv/test_repo/migrations/20250520130634_migrate_resources54.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs b/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs index 734de6d5..ccc88809 100644 --- a/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs +++ b/priv/test_repo/migrations/20250521105654_add_model_tuple_to_post.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs b/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs index 90c3fcba..264c19e2 100644 --- a/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs +++ b/priv/test_repo/migrations/20250605230457_create_record_temp_entities_table.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250612113920_migrate_resources55.exs b/priv/test_repo/migrations/20250612113920_migrate_resources55.exs index b91c4fa9..7ab54998 100644 --- a/priv/test_repo/migrations/20250612113920_migrate_resources55.exs +++ b/priv/test_repo/migrations/20250612113920_migrate_resources55.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250618011917_migrate_resources56.exs b/priv/test_repo/migrations/20250618011917_migrate_resources56.exs index 97d68971..5c4105b3 100644 --- a/priv/test_repo/migrations/20250618011917_migrate_resources56.exs +++ b/priv/test_repo/migrations/20250618011917_migrate_resources56.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs b/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs index fdcd91e4..16dd2efc 100644 --- a/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs +++ b/priv/test_repo/migrations/20250714225304_add_complex_calculations_folder_and_items.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250731124648_migrate_resources57.exs b/priv/test_repo/migrations/20250731124648_migrate_resources57.exs index 3fea4408..2c54e133 100644 --- a/priv/test_repo/migrations/20250731124648_migrate_resources57.exs +++ b/priv/test_repo/migrations/20250731124648_migrate_resources57.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250810102512_migrate_resources58.exs b/priv/test_repo/migrations/20250810102512_migrate_resources58.exs index de4bbd5a..592824b4 100644 --- a/priv/test_repo/migrations/20250810102512_migrate_resources58.exs +++ b/priv/test_repo/migrations/20250810102512_migrate_resources58.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250908073737_migrate_resources59.exs b/priv/test_repo/migrations/20250908073737_migrate_resources59.exs index dee8b387..962338ef 100644 --- a/priv/test_repo/migrations/20250908073737_migrate_resources59.exs +++ b/priv/test_repo/migrations/20250908073737_migrate_resources59.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250908093505_migrate_resources60.exs b/priv/test_repo/migrations/20250908093505_migrate_resources60.exs index d3747224..f3137214 100644 --- a/priv/test_repo/migrations/20250908093505_migrate_resources60.exs +++ b/priv/test_repo/migrations/20250908093505_migrate_resources60.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20250908212414_migrate_resources61.exs b/priv/test_repo/migrations/20250908212414_migrate_resources61.exs index 2bd3d6bf..9730c75e 100644 --- a/priv/test_repo/migrations/20250908212414_migrate_resources61.exs +++ b/priv/test_repo/migrations/20250908212414_migrate_resources61.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20251002180954_migrate_resources62.exs b/priv/test_repo/migrations/20251002180954_migrate_resources62.exs index 8fd9c180..9c2dd09c 100644 --- a/priv/test_repo/migrations/20251002180954_migrate_resources62.exs +++ b/priv/test_repo/migrations/20251002180954_migrate_resources62.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs b/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs index 5a5bd69b..6ef57f23 100644 --- a/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs +++ b/priv/test_repo/tenant_migrations/20220805191441_migrate_resources1.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT @@ -41,4 +41,4 @@ defmodule AshPostgres.TestRepo.TenantMigrations.MigrateResources1 do drop table(:multitenant_posts, prefix: prefix()) end -end \ No newline at end of file +end diff --git a/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs b/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs index 56751e6c..a97ed282 100644 --- a/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs +++ b/priv/test_repo/tenant_migrations/20240327211149_migrate_resources2.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs b/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs index 8c913034..795b70c4 100644 --- a/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs +++ b/priv/test_repo/tenant_migrations/20240610162043_migrate_resources3.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs b/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs index ab537413..b4b10a97 100644 --- a/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs +++ b/priv/test_repo/tenant_migrations/20250122203454_migrate_resources4.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs b/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs index 2a52a954..881bde93 100644 --- a/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs +++ b/priv/test_repo/tenant_migrations/20250220073135_migrate_resources5.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs b/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs index 18a7fdfd..a195cfce 100644 --- a/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs +++ b/priv/test_repo/tenant_migrations/20250220073141_migrate_resources6.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs index 6170f2f2..85509bbf 100644 --- a/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs +++ b/priv/test_repo/tenant_migrations/20251001120813_migrate_resources7.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/aggregate_test.exs b/test/aggregate_test.exs index 3e64440f..ad66b36e 100644 --- a/test/aggregate_test.exs +++ b/test/aggregate_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/ash_postgres_test.exs b/test/ash_postgres_test.exs index 41c46e31..cfb6b0d7 100644 --- a/test/ash_postgres_test.exs +++ b/test/ash_postgres_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/atomics_test.exs b/test/atomics_test.exs index b9443acf..d5680ac8 100644 --- a/test/atomics_test.exs +++ b/test/atomics_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index 420ff219..296742fd 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/bulk_destroy_test.exs b/test/bulk_destroy_test.exs index 24cf2395..cd07a684 100644 --- a/test/bulk_destroy_test.exs +++ b/test/bulk_destroy_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/bulk_update_test.exs b/test/bulk_update_test.exs index 38ce2f45..4dc7dc30 100644 --- a/test/bulk_update_test.exs +++ b/test/bulk_update_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 38e4cf5b..cdad5c18 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/cascade_destroy_test.exs b/test/cascade_destroy_test.exs index 062e0378..aa9408ac 100644 --- a/test/cascade_destroy_test.exs +++ b/test/cascade_destroy_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/combination_test.exs b/test/combination_test.exs index fdb55d35..c1143721 100644 --- a/test/combination_test.exs +++ b/test/combination_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/complex_calculations_test.exs b/test/complex_calculations_test.exs index 815071f7..39cccefc 100644 --- a/test/complex_calculations_test.exs +++ b/test/complex_calculations_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/composite_type_test.exs b/test/composite_type_test.exs index 7e73e198..edf1af9c 100644 --- a/test/composite_type_test.exs +++ b/test/composite_type_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/constraint_test.exs b/test/constraint_test.exs index 7c63f3c5..5266f194 100644 --- a/test/constraint_test.exs +++ b/test/constraint_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/create_test.exs b/test/create_test.exs index 92effd16..e605910f 100644 --- a/test/create_test.exs +++ b/test/create_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/custom_expression_test.exs b/test/custom_expression_test.exs index 7f204eef..959cbd89 100644 --- a/test/custom_expression_test.exs +++ b/test/custom_expression_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/custom_index_test.exs b/test/custom_index_test.exs index 9d02b655..7e866f62 100644 --- a/test/custom_index_test.exs +++ b/test/custom_index_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs index 02d702f5..d0911302 100644 --- a/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs +++ b/test/cve/empty_atomic_non_bulk_actions_policy_bypass_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/destroy_test.exs b/test/destroy_test.exs index 208b2038..65ee4365 100644 --- a/test/destroy_test.exs +++ b/test/destroy_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/dev_migrations_test.exs b/test/dev_migrations_test.exs index 64cbb7c1..5716fffb 100644 --- a/test/dev_migrations_test.exs +++ b/test/dev_migrations_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/distinct_test.exs b/test/distinct_test.exs index a616cac8..8e7ccaac 100644 --- a/test/distinct_test.exs +++ b/test/distinct_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/ecto_compatibility_test.exs b/test/ecto_compatibility_test.exs index 3670d021..178ea920 100644 --- a/test/ecto_compatibility_test.exs +++ b/test/ecto_compatibility_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/embeddable_resource_test.exs b/test/embeddable_resource_test.exs index d30c11c3..ba2609b0 100644 --- a/test/embeddable_resource_test.exs +++ b/test/embeddable_resource_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/enum_test.exs b/test/enum_test.exs index 0dea90a8..07ee697d 100644 --- a/test/enum_test.exs +++ b/test/enum_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/error_expr_test.exs b/test/error_expr_test.exs index 98719104..f4a4f35b 100644 --- a/test/error_expr_test.exs +++ b/test/error_expr_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/filter_child_relationship_by_parent_relationship_test.exs b/test/filter_child_relationship_by_parent_relationship_test.exs index 63a50eef..5b224ddb 100644 --- a/test/filter_child_relationship_by_parent_relationship_test.exs +++ b/test/filter_child_relationship_by_parent_relationship_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/filter_field_policy_test.exs b/test/filter_field_policy_test.exs index 6778e70b..a6485bf9 100644 --- a/test/filter_field_policy_test.exs +++ b/test/filter_field_policy_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/filter_test.exs b/test/filter_test.exs index 0172fb81..5492864b 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/load_test.exs b/test/load_test.exs index fa9c8aba..64300139 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/lock_test.exs b/test/lock_test.exs index 01122f50..40cebf44 100644 --- a/test/lock_test.exs +++ b/test/lock_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/ltree_test.exs b/test/ltree_test.exs index 33b3c1ba..36de46fc 100644 --- a/test/ltree_test.exs +++ b/test/ltree_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/manual_relationships_test.exs b/test/manual_relationships_test.exs index 3f4af170..b1a7689b 100644 --- a/test/manual_relationships_test.exs +++ b/test/manual_relationships_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/manual_update_test.exs b/test/manual_update_test.exs index 74a6ec35..bfc5e322 100644 --- a/test/manual_update_test.exs +++ b/test/manual_update_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/many_to_many_expr_test.exs b/test/many_to_many_expr_test.exs index 942168fc..4067fb51 100644 --- a/test/many_to_many_expr_test.exs +++ b/test/many_to_many_expr_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/migration_generator_test.exs b/test/migration_generator_test.exs index 41b16774..b3d1fd17 100644 --- a/test/migration_generator_test.exs +++ b/test/migration_generator_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/mix/tasks/ash_postgres.install_test.exs b/test/mix/tasks/ash_postgres.install_test.exs index e8ac1154..44e00c46 100644 --- a/test/mix/tasks/ash_postgres.install_test.exs +++ b/test/mix/tasks/ash_postgres.install_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/mix_squash_snapshots_test.exs b/test/mix_squash_snapshots_test.exs index 352430ae..68f34973 100644 --- a/test/mix_squash_snapshots_test.exs +++ b/test/mix_squash_snapshots_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/multi_domain_calculations_test.exs b/test/multi_domain_calculations_test.exs index 54652f63..fd9a8059 100644 --- a/test/multi_domain_calculations_test.exs +++ b/test/multi_domain_calculations_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/multitenancy_test.exs b/test/multitenancy_test.exs index 3e17c1ad..56ee9ef6 100644 --- a/test/multitenancy_test.exs +++ b/test/multitenancy_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/parent_filter_test.exs b/test/parent_filter_test.exs index 42f8326e..4e364211 100644 --- a/test/parent_filter_test.exs +++ b/test/parent_filter_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/parent_sort_test.exs b/test/parent_sort_test.exs index 7d472f2d..b1532416 100644 --- a/test/parent_sort_test.exs +++ b/test/parent_sort_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/polymorphism_test.exs b/test/polymorphism_test.exs index ecd8dc9d..630b7da1 100644 --- a/test/polymorphism_test.exs +++ b/test/polymorphism_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/primary_key_test.exs b/test/primary_key_test.exs index 8812bc0e..792520c3 100644 --- a/test/primary_key_test.exs +++ b/test/primary_key_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/references_test.exs b/test/references_test.exs index 59151ba3..49a661bd 100644 --- a/test/references_test.exs +++ b/test/references_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/rel_with_parent_filter_test.exs b/test/rel_with_parent_filter_test.exs index bdcc301d..ba178713 100644 --- a/test/rel_with_parent_filter_test.exs +++ b/test/rel_with_parent_filter_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/resource_generator_test.exs b/test/resource_generator_test.exs index 4c1f916c..1c793ba0 100644 --- a/test/resource_generator_test.exs +++ b/test/resource_generator_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/schema_test.exs b/test/schema_test.exs index be04d3bb..235a027a 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/select_test.exs b/test/select_test.exs index 7c440b10..cf21a6cb 100644 --- a/test/select_test.exs +++ b/test/select_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/sort_test.exs b/test/sort_test.exs index c1afc99a..da34a304 100644 --- a/test/sort_test.exs +++ b/test/sort_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs index efdb0195..ad414bf8 100644 --- a/test/storage_types_test.exs +++ b/test/storage_types_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/subquery_test.exs b/test/subquery_test.exs index 9795730b..6d4da873 100644 --- a/test/subquery_test.exs +++ b/test/subquery_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/domain.ex b/test/support/complex_calculations/domain.ex index 6356daa3..bf5b416d 100644 --- a/test/support/complex_calculations/domain.ex +++ b/test/support/complex_calculations/domain.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/certification.ex b/test/support/complex_calculations/resources/certification.ex index 317a5dc1..ea5ae573 100644 --- a/test/support/complex_calculations/resources/certification.ex +++ b/test/support/complex_calculations/resources/certification.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/channel.ex b/test/support/complex_calculations/resources/channel.ex index af99ed29..a59f14bf 100644 --- a/test/support/complex_calculations/resources/channel.ex +++ b/test/support/complex_calculations/resources/channel.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/channel_member.ex b/test/support/complex_calculations/resources/channel_member.ex index 7feb3c9d..ba4d1b8f 100644 --- a/test/support/complex_calculations/resources/channel_member.ex +++ b/test/support/complex_calculations/resources/channel_member.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/dm_channel.ex b/test/support/complex_calculations/resources/dm_channel.ex index 2113a3d5..2dd67b9b 100644 --- a/test/support/complex_calculations/resources/dm_channel.ex +++ b/test/support/complex_calculations/resources/dm_channel.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/documentation.ex b/test/support/complex_calculations/resources/documentation.ex index 88a3b7df..aadc0c81 100644 --- a/test/support/complex_calculations/resources/documentation.ex +++ b/test/support/complex_calculations/resources/documentation.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/folder.ex b/test/support/complex_calculations/resources/folder.ex index c0faefe6..5b131c25 100644 --- a/test/support/complex_calculations/resources/folder.ex +++ b/test/support/complex_calculations/resources/folder.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/folder_item.ex b/test/support/complex_calculations/resources/folder_item.ex index 7404970e..38f7cac7 100644 --- a/test/support/complex_calculations/resources/folder_item.ex +++ b/test/support/complex_calculations/resources/folder_item.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/complex_calculations/resources/skill.ex b/test/support/complex_calculations/resources/skill.ex index 44e8dfff..20874506 100644 --- a/test/support/complex_calculations/resources/skill.ex +++ b/test/support/complex_calculations/resources/skill.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/concat.ex b/test/support/concat.ex index 50b34373..cb2ffd7c 100644 --- a/test/support/concat.ex +++ b/test/support/concat.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/dev_test_repo.ex b/test/support/dev_test_repo.ex index c69abe43..23fcbe37 100644 --- a/test/support/dev_test_repo.ex +++ b/test/support/dev_test_repo.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/domain.ex b/test/support/domain.ex index ecd82d96..2fb81323 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_one.ex b/test/support/multi_domain_calculations/domain_one.ex index b5e091f4..0414e955 100644 --- a/test/support/multi_domain_calculations/domain_one.ex +++ b/test/support/multi_domain_calculations/domain_one.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_one/item.ex b/test/support/multi_domain_calculations/domain_one/item.ex index fa07ac9a..709ef7a0 100644 --- a/test/support/multi_domain_calculations/domain_one/item.ex +++ b/test/support/multi_domain_calculations/domain_one/item.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_three.ex b/test/support/multi_domain_calculations/domain_three.ex index a452e013..4689f0a5 100644 --- a/test/support/multi_domain_calculations/domain_three.ex +++ b/test/support/multi_domain_calculations/domain_three.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_three/relationship_item.ex b/test/support/multi_domain_calculations/domain_three/relationship_item.ex index 9c15eb0c..67185dfc 100644 --- a/test/support/multi_domain_calculations/domain_three/relationship_item.ex +++ b/test/support/multi_domain_calculations/domain_three/relationship_item.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_two.ex b/test/support/multi_domain_calculations/domain_two.ex index 715960fb..8ff13982 100644 --- a/test/support/multi_domain_calculations/domain_two.ex +++ b/test/support/multi_domain_calculations/domain_two.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_two/other_item.ex b/test/support/multi_domain_calculations/domain_two/other_item.ex index b7e65fd7..be9d7e95 100644 --- a/test/support/multi_domain_calculations/domain_two/other_item.ex +++ b/test/support/multi_domain_calculations/domain_two/other_item.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multi_domain_calculations/domain_two/sub_item.ex b/test/support/multi_domain_calculations/domain_two/sub_item.ex index 76550194..941368e5 100644 --- a/test/support/multi_domain_calculations/domain_two/sub_item.ex +++ b/test/support/multi_domain_calculations/domain_two/sub_item.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/domain.ex b/test/support/multitenancy/domain.ex index 1ee5e90b..c711e7be 100644 --- a/test/support/multitenancy/domain.ex +++ b/test/support/multitenancy/domain.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/composite_key_post.ex b/test/support/multitenancy/resources/composite_key_post.ex index b7856998..55bc4b45 100644 --- a/test/support/multitenancy/resources/composite_key_post.ex +++ b/test/support/multitenancy/resources/composite_key_post.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/cross_tenant_post_link.ex b/test/support/multitenancy/resources/cross_tenant_post_link.ex index e79e0cf8..eee4b60f 100644 --- a/test/support/multitenancy/resources/cross_tenant_post_link.ex +++ b/test/support/multitenancy/resources/cross_tenant_post_link.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/dev_migrations_org.ex b/test/support/multitenancy/resources/dev_migrations_org.ex index 0cc59783..33d19785 100644 --- a/test/support/multitenancy/resources/dev_migrations_org.ex +++ b/test/support/multitenancy/resources/dev_migrations_org.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/named_org.ex b/test/support/multitenancy/resources/named_org.ex index 714c090d..0b800106 100644 --- a/test/support/multitenancy/resources/named_org.ex +++ b/test/support/multitenancy/resources/named_org.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/non_multitenant_post_link.ex b/test/support/multitenancy/resources/non_multitenant_post_link.ex index 8f2a990e..e8e0cca6 100644 --- a/test/support/multitenancy/resources/non_multitenant_post_link.ex +++ b/test/support/multitenancy/resources/non_multitenant_post_link.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex index 0eb78386..79478246 100644 --- a/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex +++ b/test/support/multitenancy/resources/non_multitenant_post_multitenant_link.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/org.ex b/test/support/multitenancy/resources/org.ex index 21638793..66e75a23 100644 --- a/test/support/multitenancy/resources/org.ex +++ b/test/support/multitenancy/resources/org.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/post.ex b/test/support/multitenancy/resources/post.ex index cab27827..871e504b 100644 --- a/test/support/multitenancy/resources/post.ex +++ b/test/support/multitenancy/resources/post.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/post_link.ex b/test/support/multitenancy/resources/post_link.ex index dcdfdc41..6d45a2ae 100644 --- a/test/support/multitenancy/resources/post_link.ex +++ b/test/support/multitenancy/resources/post_link.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/multitenancy/resources/user.ex b/test/support/multitenancy/resources/user.ex index 7406eb07..0713b966 100644 --- a/test/support/multitenancy/resources/user.ex +++ b/test/support/multitenancy/resources/user.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/relationships/comments_containing_title.ex b/test/support/relationships/comments_containing_title.ex index e17ee933..156d4ec7 100644 --- a/test/support/relationships/comments_containing_title.ex +++ b/test/support/relationships/comments_containing_title.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/repo_case.ex b/test/support/repo_case.ex index 708e2d42..5e94fe98 100644 --- a/test/support/repo_case.ex +++ b/test/support/repo_case.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/account.ex b/test/support/resources/account.ex index 87e6544a..cf4b7f30 100644 --- a/test/support/resources/account.ex +++ b/test/support/resources/account.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 097a771c..16ca5a8e 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/bio.ex b/test/support/resources/bio.ex index 8c0b33f0..e173bc05 100644 --- a/test/support/resources/bio.ex +++ b/test/support/resources/bio.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/chat.ex b/test/support/resources/chat.ex index 5c04c1af..c237fe42 100644 --- a/test/support/resources/chat.ex +++ b/test/support/resources/chat.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/co_authored_post.ex b/test/support/resources/co_authored_post.ex index 022c9eca..c49c3c2b 100644 --- a/test/support/resources/co_authored_post.ex +++ b/test/support/resources/co_authored_post.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/comedian.ex b/test/support/resources/comedian.ex index 2404d56c..f2b14cbf 100644 --- a/test/support/resources/comedian.ex +++ b/test/support/resources/comedian.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index cbbc6abb..133005db 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/comment_link.ex b/test/support/resources/comment_link.ex index 5caab846..42fba683 100644 --- a/test/support/resources/comment_link.ex +++ b/test/support/resources/comment_link.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/content.ex b/test/support/resources/content.ex index 8b823644..4e57c7be 100644 --- a/test/support/resources/content.ex +++ b/test/support/resources/content.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/content_visibility_group.ex b/test/support/resources/content_visibility_group.ex index 797d7c7e..b261f1f3 100644 --- a/test/support/resources/content_visibility_group.ex +++ b/test/support/resources/content_visibility_group.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/csv.ex b/test/support/resources/csv.ex index ccf5b418..c4e14393 100644 --- a/test/support/resources/csv.ex +++ b/test/support/resources/csv.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/customer.ex b/test/support/resources/customer.ex index 834fcead..0ab674b9 100644 --- a/test/support/resources/customer.ex +++ b/test/support/resources/customer.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/db_point.ex b/test/support/resources/db_point.ex index cf018f4a..e9e20131 100644 --- a/test/support/resources/db_point.ex +++ b/test/support/resources/db_point.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/db_string_point.ex b/test/support/resources/db_string_point.ex index e78c7536..48edf2dd 100644 --- a/test/support/resources/db_string_point.ex +++ b/test/support/resources/db_string_point.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/entity.ex b/test/support/resources/entity.ex index c693d02a..18c1b3f2 100644 --- a/test/support/resources/entity.ex +++ b/test/support/resources/entity.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/integer_post.ex b/test/support/resources/integer_post.ex index 2237e723..d270a933 100644 --- a/test/support/resources/integer_post.ex +++ b/test/support/resources/integer_post.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/invite.ex b/test/support/resources/invite.ex index 06d3ea70..07456bfc 100644 --- a/test/support/resources/invite.ex +++ b/test/support/resources/invite.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/joke.ex b/test/support/resources/joke.ex index 16414cb4..b66c1970 100644 --- a/test/support/resources/joke.ex +++ b/test/support/resources/joke.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/manager.ex b/test/support/resources/manager.ex index e5b2cce9..9ef2a0a1 100644 --- a/test/support/resources/manager.ex +++ b/test/support/resources/manager.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/message.ex b/test/support/resources/message.ex index 4cbc9533..1085c337 100644 --- a/test/support/resources/message.ex +++ b/test/support/resources/message.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/note.ex b/test/support/resources/note.ex index 0facb927..98ccef89 100644 --- a/test/support/resources/note.ex +++ b/test/support/resources/note.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/order.ex b/test/support/resources/order.ex index fead194c..76c7ef6f 100644 --- a/test/support/resources/order.ex +++ b/test/support/resources/order.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/organization.ex b/test/support/resources/organization.ex index d375a2db..e5dfd492 100644 --- a/test/support/resources/organization.ex +++ b/test/support/resources/organization.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/permalink.ex b/test/support/resources/permalink.ex index adca302f..e3aadaa9 100644 --- a/test/support/resources/permalink.ex +++ b/test/support/resources/permalink.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 2b85570a..c15b6b0e 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/post_follower.ex b/test/support/resources/post_follower.ex index 9e71b398..aa28f149 100644 --- a/test/support/resources/post_follower.ex +++ b/test/support/resources/post_follower.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/post_link.ex b/test/support/resources/post_link.ex index bc6390c8..147e09a9 100644 --- a/test/support/resources/post_link.ex +++ b/test/support/resources/post_link.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/post_tag.ex b/test/support/resources/post_tag.ex index 2ae47876..96306f91 100644 --- a/test/support/resources/post_tag.ex +++ b/test/support/resources/post_tag.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/post_views.ex b/test/support/resources/post_views.ex index c3b3aaaa..37bc2f54 100644 --- a/test/support/resources/post_views.ex +++ b/test/support/resources/post_views.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/post_with_empty_update.ex b/test/support/resources/post_with_empty_update.ex index 06693738..2e808ea4 100644 --- a/test/support/resources/post_with_empty_update.ex +++ b/test/support/resources/post_with_empty_update.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/product.ex b/test/support/resources/product.ex index fa2bbee9..194f0586 100644 --- a/test/support/resources/product.ex +++ b/test/support/resources/product.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/profile.ex b/test/support/resources/profile.ex index b06bab6f..9a14ee30 100644 --- a/test/support/resources/profile.ex +++ b/test/support/resources/profile.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/punchline.ex b/test/support/resources/punchline.ex index 66da6063..701fe540 100644 --- a/test/support/resources/punchline.ex +++ b/test/support/resources/punchline.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/rating.ex b/test/support/resources/rating.ex index 1fab32b1..b4ac159c 100644 --- a/test/support/resources/rating.ex +++ b/test/support/resources/rating.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/record.ex b/test/support/resources/record.ex index 0045db83..658b5f3d 100644 --- a/test/support/resources/record.ex +++ b/test/support/resources/record.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/record_temp_entity.ex b/test/support/resources/record_temp_entity.ex index ba0f73e3..c44bfdbf 100644 --- a/test/support/resources/record_temp_entity.ex +++ b/test/support/resources/record_temp_entity.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/role.ex b/test/support/resources/role.ex index df3635be..894e83bc 100644 --- a/test/support/resources/role.ex +++ b/test/support/resources/role.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/rsvp.ex b/test/support/resources/rsvp.ex index f364c333..af39eab0 100644 --- a/test/support/resources/rsvp.ex +++ b/test/support/resources/rsvp.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/settings.ex b/test/support/resources/settings.ex index e348f847..b18acb2c 100644 --- a/test/support/resources/settings.ex +++ b/test/support/resources/settings.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/staff_group.ex b/test/support/resources/staff_group.ex index 4aafeca8..dc3a23b1 100644 --- a/test/support/resources/staff_group.ex +++ b/test/support/resources/staff_group.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/staff_group_member.ex b/test/support/resources/staff_group_member.ex index a9ce9e97..fad757a6 100644 --- a/test/support/resources/staff_group_member.ex +++ b/test/support/resources/staff_group_member.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/standup_club.ex b/test/support/resources/standup_club.ex index ffa91044..af3047a2 100644 --- a/test/support/resources/standup_club.ex +++ b/test/support/resources/standup_club.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/stateful_post_follwer.ex b/test/support/resources/stateful_post_follwer.ex index cb66359c..017e979c 100644 --- a/test/support/resources/stateful_post_follwer.ex +++ b/test/support/resources/stateful_post_follwer.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/subquery/access.ex b/test/support/resources/subquery/access.ex index 9c971486..2ef87bb7 100644 --- a/test/support/resources/subquery/access.ex +++ b/test/support/resources/subquery/access.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/subquery/child.ex b/test/support/resources/subquery/child.ex index 5a9cd6af..4a078ebd 100644 --- a/test/support/resources/subquery/child.ex +++ b/test/support/resources/subquery/child.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/subquery/child_domain.ex b/test/support/resources/subquery/child_domain.ex index ab33df34..3ab11eef 100644 --- a/test/support/resources/subquery/child_domain.ex +++ b/test/support/resources/subquery/child_domain.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/subquery/parent.ex b/test/support/resources/subquery/parent.ex index a72c0054..66b5d0ea 100644 --- a/test/support/resources/subquery/parent.ex +++ b/test/support/resources/subquery/parent.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/subquery/parent_domain.ex b/test/support/resources/subquery/parent_domain.ex index d2e200d3..77d3d246 100644 --- a/test/support/resources/subquery/parent_domain.ex +++ b/test/support/resources/subquery/parent_domain.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/subquery/through.ex b/test/support/resources/subquery/through.ex index 3c0f4e75..a0e59604 100644 --- a/test/support/resources/subquery/through.ex +++ b/test/support/resources/subquery/through.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/tag.ex b/test/support/resources/tag.ex index 66bec825..b29a2cd0 100644 --- a/test/support/resources/tag.ex +++ b/test/support/resources/tag.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/temp_entity.ex b/test/support/resources/temp_entity.ex index 5507836e..308c6f75 100644 --- a/test/support/resources/temp_entity.ex +++ b/test/support/resources/temp_entity.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/resources/user.ex b/test/support/resources/user.ex index 1009dccd..8dd88866 100644 --- a/test/support/resources/user.ex +++ b/test/support/resources/user.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/string_agg.ex b/test/support/string_agg.ex index e6f92821..9cc5a8da 100644 --- a/test/support/string_agg.ex +++ b/test/support/string_agg.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/test_app.ex b/test/support/test_app.ex index 97d98fc2..bcc80e91 100644 --- a/test/support/test_app.ex +++ b/test/support/test_app.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/test_custom_extension.ex b/test/support/test_custom_extension.ex index bd6d26d3..35955d79 100644 --- a/test/support/test_custom_extension.ex +++ b/test/support/test_custom_extension.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index 14ec8160..fb1437d6 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index 8dbc64c1..cf45c646 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/trigram_word_similarity.ex b/test/support/trigram_word_similarity.ex index 77c267c7..7b23166b 100644 --- a/test/support/trigram_word_similarity.ex +++ b/test/support/trigram_word_similarity.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/composite_point.ex b/test/support/types/composite_point.ex index b82887b9..db660894 100644 --- a/test/support/types/composite_point.ex +++ b/test/support/types/composite_point.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/email.ex b/test/support/types/email.ex index 9f552b6d..5f7412ea 100644 --- a/test/support/types/email.ex +++ b/test/support/types/email.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/money.ex b/test/support/types/money.ex index 6b2d79f8..09cc162d 100644 --- a/test/support/types/money.ex +++ b/test/support/types/money.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/person_detail.ex b/test/support/types/person_detail.ex index 3666300f..5b80861a 100644 --- a/test/support/types/person_detail.ex +++ b/test/support/types/person_detail.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/point.ex b/test/support/types/point.ex index 819d0287..7647688c 100644 --- a/test/support/types/point.ex +++ b/test/support/types/point.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/response.ex b/test/support/types/response.ex index 9428ee15..8c20dbf3 100644 --- a/test/support/types/response.ex +++ b/test/support/types/response.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/status.ex b/test/support/types/status.ex index 7453e955..b6eab647 100644 --- a/test/support/types/status.ex +++ b/test/support/types/status.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/status_enum.ex b/test/support/types/status_enum.ex index 2a845ff2..ac93d3b9 100644 --- a/test/support/types/status_enum.ex +++ b/test/support/types/status_enum.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/status_enum_no_cast.ex b/test/support/types/status_enum_no_cast.ex index 032266de..94cc6125 100644 --- a/test/support/types/status_enum_no_cast.ex +++ b/test/support/types/status_enum_no_cast.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/types/string_point.ex b/test/support/types/string_point.ex index 1be38e28..42430c6d 100644 --- a/test/support/types/string_point.ex +++ b/test/support/types/string_point.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/unrelated_aggregates/profile.ex b/test/support/unrelated_aggregates/profile.ex index 257a44e3..4f17f63e 100644 --- a/test/support/unrelated_aggregates/profile.ex +++ b/test/support/unrelated_aggregates/profile.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/unrelated_aggregates/report.ex b/test/support/unrelated_aggregates/report.ex index 025e550c..4619aa12 100644 --- a/test/support/unrelated_aggregates/report.ex +++ b/test/support/unrelated_aggregates/report.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/unrelated_aggregates/secure_profile.ex b/test/support/unrelated_aggregates/secure_profile.ex index 7f380da2..f345769b 100644 --- a/test/support/unrelated_aggregates/secure_profile.ex +++ b/test/support/unrelated_aggregates/secure_profile.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/support/unrelated_aggregates/user.ex b/test/support/unrelated_aggregates/user.ex index 22529079..f3a20de8 100644 --- a/test/support/unrelated_aggregates/user.ex +++ b/test/support/unrelated_aggregates/user.ex @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/test_helper.exs b/test/test_helper.exs index aba4a697..81659b92 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/transaction_test.exs b/test/transaction_test.exs index a4403a16..7939024f 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/tuple_test.exs b/test/tuple_test.exs index 5901f5c4..364c0e69 100644 --- a/test/tuple_test.exs +++ b/test/tuple_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/type_test.exs b/test/type_test.exs index 2e7c3132..99c866ac 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/unique_identity_test.exs b/test/unique_identity_test.exs index 618ead15..10c3d4aa 100644 --- a/test/unique_identity_test.exs +++ b/test/unique_identity_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/unrelated_aggregates_test.exs b/test/unrelated_aggregates_test.exs index 1c4ca107..82d466ae 100644 --- a/test/unrelated_aggregates_test.exs +++ b/test/unrelated_aggregates_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/update_test.exs b/test/update_test.exs index 22fb4af2..17756840 100644 --- a/test/update_test.exs +++ b/test/update_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT diff --git a/test/upsert_test.exs b/test/upsert_test.exs index 0f3308a7..dc1accaa 100644 --- a/test/upsert_test.exs +++ b/test/upsert_test.exs @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020 Zach Daniel +# SPDX-FileCopyrightText: 2019 ash_postgres contributors # # SPDX-License-Identifier: MIT From f44cee5e185da7dff4ddb26b8bd65bbc78e75238 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 14 Oct 2025 22:23:15 -0400 Subject: [PATCH 1200/1215] chore: check `upsert?` as well inside of new code not technically necessary but doesn't hurt --- lib/data_layer.ex | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 894de3fb..7dbb19d2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2042,7 +2042,7 @@ defmodule AshPostgres.DataLayer do # if it's single the return_skipped_upsert? is handled at the # call site https://github.com/ash-project/ash_postgres/blob/0b21d4a99cc3f6d8676947e291ac9b9d57ad6e2e/lib/data_layer.ex#L3046-L3046 result = - if options[:return_skipped_upsert?] && !opts[:single?] do + if options[:upsert?] && options[:return_skipped_upsert?] && !opts[:single?] do [changeset | _] = changesets results_by_identity = diff --git a/mix.lock b/mix.lock index 96080205..03a5fd8e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.6.2", "90d1c8296be777b90caabf51b99323d6618a0b92594dfab92b02bdf848ac38bf", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3546b5798dd24576cc451f6e03f3d6e3bb62666c0921bfe8aae700c599d9c38d"}, + "ash": {:hex, :ash, "3.6.3", "526ec1989a6be80798f585a8ad9e08752330e8a5e32e223fe7161955f994dfd4", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23f9b85d545aee27011e371ffc4a19f7af9195e89c2038e50007084cdd1d31b5"}, "ash_sql": {:hex, :ash_sql, "0.3.4", "c8c0446fbd6d3e6920f793b971c83ba3d14f96095036366d313b72656400509d", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "4edb1fb707048a41f7944274b1a0b571aa3c9117b8a7a12809ca023b6f955cb4"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, From eb3f91682464e29a971dc3e188093d841facc55c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 14 Oct 2025 23:08:03 -0400 Subject: [PATCH 1201/1215] chore: cleanup function_exported? checks --- lib/migration_generator/migration_generator.ex | 2 +- lib/sql_implementation.ex | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 6cdbc469..9881818c 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -3227,7 +3227,7 @@ defmodule AshPostgres.MigrationGenerator do migration_type(attribute.type, attribute.constraints) type = - if :erlang.function_exported(repo, :override_migration_type, 1) do + if function_exported?(repo, :override_migration_type, 1) do repo.override_migration_type(type) else type diff --git a/lib/sql_implementation.ex b/lib/sql_implementation.ex index 3aed4340..fae3fede 100644 --- a/lib/sql_implementation.ex +++ b/lib/sql_implementation.ex @@ -311,11 +311,7 @@ defmodule AshPostgres.SqlImplementation do def parameterized_type(type, constraints) do if Ash.Type.ash_type?(type) do cast_in_query? = - if function_exported?(Ash.Type, :cast_in_query?, 2) do - Ash.Type.cast_in_query?(type, constraints) - else - Ash.Type.cast_in_query?(type) - end + Ash.Type.cast_in_query?(type, constraints) if cast_in_query? do type = Ash.Type.ecto_type(type) From 7375bfb0d90138e4dd943848ae49102bca39f5ae Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Oct 2025 18:36:47 -0400 Subject: [PATCH 1202/1215] improvement: implement combination_acc/1 --- lib/data_layer.ex | 3 +++ mix.exs | 4 ++-- mix.lock | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 7dbb19d2..3bad7f45 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -889,6 +889,9 @@ defmodule AshPostgres.DataLayer do end end + @impl true + def combination_acc(query), do: AshSql.Query.combination_acc(query) + @impl true def run_aggregate_query(original_query, aggregates, resource) do AshSql.AggregateQuery.run_aggregate_query( diff --git a/mix.exs b/mix.exs index 2c0f979c..9c70ce1d 100644 --- a/mix.exs +++ b/mix.exs @@ -177,9 +177,9 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.5 and >= 3.6.2")}, + {:ash, ash_version("~> 3.7")}, {:spark, "~> 2.3 and >= 2.3.4"}, - {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.2")}, + {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.6")}, {:igniter, "~> 0.6 and >= 0.6.29", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index 03a5fd8e..dd24d9a9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.6.3", "526ec1989a6be80798f585a8ad9e08752330e8a5e32e223fe7161955f994dfd4", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23f9b85d545aee27011e371ffc4a19f7af9195e89c2038e50007084cdd1d31b5"}, - "ash_sql": {:hex, :ash_sql, "0.3.4", "c8c0446fbd6d3e6920f793b971c83ba3d14f96095036366d313b72656400509d", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "4edb1fb707048a41f7944274b1a0b571aa3c9117b8a7a12809ca023b6f955cb4"}, + "ash_sql": {:hex, :ash_sql, "0.3.6", "6036e57243448b1cc20f8afa0b8f6dcdbd14d4ccc4aa32e7feb5ca2465b3e13a", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "2cab83c09bc0a3d32c545691f35bb94199b4058dca6e254e8d99532d995fb8f7"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, @@ -41,7 +41,7 @@ "reactor": {:hex, :reactor, "0.17.0", "eb8bdb530dbae824e2d36a8538f8ec4f3aa7c2d1b61b04959fa787c634f88b49", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3c3bf71693adbad9117b11ec83cfed7d5851b916ade508ed9718de7ae165bf25"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, - "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, + "simple_sat": {:hex, :simple_sat, "0.1.4", "39baf72cdca14f93c0b6ce2b6418b72bbb67da98fa9ca4384e2f79bbc299899d", [:mix], [], "hexpm", "3569b68e346a5fd7154b8d14173ff8bcc829f2eb7b088c30c3f42a383443930b"}, "sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spark": {:hex, :spark, "2.3.5", "f30d30ecc3b4ab9b932d9aada66af7677fc1f297a2c349b0bcec3eafb9f996e8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0e9d339704d5d148f77f2b2fef3bcfc873a9e9bb4224fcf289c545d65827202f"}, From 9bc0bede7f8d482d5c8e85b54cabbed61fa68629 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Oct 2025 18:42:01 -0400 Subject: [PATCH 1203/1215] chore: update ash --- mix.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index dd24d9a9..7b15d0ea 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,10 @@ %{ - "ash": {:hex, :ash, "3.6.3", "526ec1989a6be80798f585a8ad9e08752330e8a5e32e223fe7161955f994dfd4", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23f9b85d545aee27011e371ffc4a19f7af9195e89c2038e50007084cdd1d31b5"}, - "ash_sql": {:hex, :ash_sql, "0.3.6", "6036e57243448b1cc20f8afa0b8f6dcdbd14d4ccc4aa32e7feb5ca2465b3e13a", [:mix], [{:ash, ">= 3.5.43 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "2cab83c09bc0a3d32c545691f35bb94199b4058dca6e254e8d99532d995fb8f7"}, + "ash": {:hex, :ash, "3.7.0", "711b9eb200f81e0a071e8fe52272fde27e3548a5f3d30589dfcf23eaf34b0d12", [:mix], [{:crux, "~> 0.1.0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2581e3e2ad21aff8157b25c0f58c884524bee8d06ae8dc94d38ef0b8ae67b2a3"}, + "ash_sql": {:hex, :ash_sql, "0.3.7", "80affa5446075d71deb157c67290685a84b392d723be766bfb684f58fe0143de", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ce4d974b8e784171c5a2a62593b3672b42dfd4888fa2239f01a6b32bad769038"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, + "crux": {:hex, :crux, "0.1.1", "94f2f97d2a6079ae3c57f356412bc3b307f9579a80e43f526447b1d508dd4a72", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "e59d498f038193cbe31e448f9199f5b4c53a4c67cece9922bb839595189dd2b6"}, "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, From f48e1a4f0a210c822e564c8834e5a494272b5562 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Oct 2025 18:51:47 -0400 Subject: [PATCH 1204/1215] chore: update ash_sql --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 9c70ce1d..9463f940 100644 --- a/mix.exs +++ b/mix.exs @@ -179,7 +179,7 @@ defmodule AshPostgres.MixProject do [ {:ash, ash_version("~> 3.7")}, {:spark, "~> 2.3 and >= 2.3.4"}, - {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.6")}, + {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.7")}, {:igniter, "~> 0.6 and >= 0.6.29", optional: true}, {:ecto_sql, "~> 3.13"}, {:ecto, "~> 3.13"}, From d54f93c1937962fe17f3a412c91d144f1b8abfb7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 15 Oct 2025 18:51:57 -0400 Subject: [PATCH 1205/1215] chore: release version v2.6.23 --- CHANGELOG.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67570853..cd50c84d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.23](https://github.com/ash-project/ash_postgres/compare/v2.6.22...v2.6.23) (2025-10-15) + + + + +### Improvements: + +* implement combination_acc/1 by Zach Daniel + ## [v2.6.22](https://github.com/ash-project/ash_postgres/compare/v2.6.21...v2.6.22) (2025-10-14) diff --git a/mix.exs b/mix.exs index 9463f940..5e14da8d 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.22" + @version "2.6.23" def project do [ From f9bd1715b5aa8981caff4887495f376e18dfbf5f Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Thu, 16 Oct 2025 21:16:58 +0200 Subject: [PATCH 1206/1215] fix: handle results that can't be mapped to the changeset in bulk_create (#638) * fix: handle results that can't be mapped to the changeset in bulk_create If the identity used has attibutes that can be generated by the datalayer, we can't map the result back to the changeset and we need to just zip the results with the changesets and return them that way. * refactor: do a simple check for `upsert?` instead --- lib/data_layer.ex | 49 ++++++++++++++++++++++++++++----------- test/bulk_create_test.exs | 14 ++++++++++- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 3bad7f45..b984c4db 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2124,34 +2124,57 @@ defmodule AshPostgres.DataLayer do end) results = - changesets - |> Enum.map(fn changeset -> - identity = - changeset.attributes - |> Map.take(keys) + if opts[:upsert?] do + changesets + |> Enum.map(fn changeset -> + identity = + changeset.attributes + |> Map.take(keys) - result_for_changeset = Map.get(results_by_identity, identity) + result_for_changeset = Map.get(results_by_identity, identity) - if result_for_changeset do + if result_for_changeset do + if !opts[:upsert?] do + maybe_create_tenant!(resource, result_for_changeset) + end + + case get_bulk_operation_metadata(changeset) do + {index, metadata_key} -> + Ash.Resource.put_metadata(result_for_changeset, metadata_key, index) + + nil -> + # Compatibility fallback + Ash.Resource.put_metadata( + result_for_changeset, + :bulk_create_index, + changeset.context[:bulk_create][:index] + ) + end + end + end) + |> Enum.filter(& &1) + else + results + |> Enum.zip(changesets) + |> Enum.map(fn {result, changeset} -> if !opts[:upsert?] do - maybe_create_tenant!(resource, result_for_changeset) + maybe_create_tenant!(resource, result) end case get_bulk_operation_metadata(changeset) do {index, metadata_key} -> - Ash.Resource.put_metadata(result_for_changeset, metadata_key, index) + Ash.Resource.put_metadata(result, metadata_key, index) nil -> # Compatibility fallback Ash.Resource.put_metadata( - result_for_changeset, + result, :bulk_create_index, changeset.context[:bulk_create][:index] ) end - end - end) - |> Enum.filter(& &1) + end) + end {:ok, results} end diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index 296742fd..c18d8d1a 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -4,7 +4,7 @@ defmodule AshPostgres.BulkCreateTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Post, Record} + alias AshPostgres.Test.{IntegerPost, Post, Record} require Ash.Query import Ash.Expr @@ -356,6 +356,18 @@ defmodule AshPostgres.BulkCreateTest do |> Ash.Query.load(:ratings) |> Ash.read!() end + + test "bulk creates with integer primary key return records" do + %Ash.BulkResult{records: records} = + Ash.bulk_create!( + [%{title: "first"}, %{title: "second"}, %{title: "third"}], + IntegerPost, + :create, + return_records?: true + ) + + assert length(records) == 3 + end end describe "validation errors" do From 39475a7a278b4eb0731683bee9fca1f7e96ba611 Mon Sep 17 00:00:00 2001 From: Steve Brambilla Date: Fri, 17 Oct 2025 17:55:17 -0400 Subject: [PATCH 1207/1215] * feat: support building error payloads using immutable functions (#639) Citus compatibility: replace `jsonb_build_object` with immutable functions when `immutable_expr_error?` is true --- lib/extensions/immutable_raise_error.ex | 153 ++++++++---- .../test_repo/extensions.json | 1 + .../20251015134240.json | 226 ++++++++++++++++++ .../20251015134240.json.license | 3 + ...all_immutable_raise_error_v2_extension.exs | 69 ++++++ .../20251015134240_migrate_resources63.exs | 39 +++ test/immutable_raise_error_test.exs | 124 ++++++++++ test/support/domain.ex | 1 + .../resources/immutable_error_tester/error.ex | 31 +++ .../immutable_error_tester.ex | 67 ++++++ .../immutable_error_tester/struct.ex | 14 ++ .../validations/update_literal.ex | 33 +++ .../validations/update_many.ex | 45 ++++ .../validations/update_one.ex | 32 +++ test/support/test_repo.ex | 14 +- 15 files changed, 799 insertions(+), 53 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json create mode 100644 priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json.license create mode 100644 priv/test_repo/migrations/20251015134238_install_immutable_raise_error_v2_extension.exs create mode 100644 priv/test_repo/migrations/20251015134240_migrate_resources63.exs create mode 100644 test/immutable_raise_error_test.exs create mode 100644 test/support/resources/immutable_error_tester/error.ex create mode 100644 test/support/resources/immutable_error_tester/immutable_error_tester.ex create mode 100644 test/support/resources/immutable_error_tester/struct.ex create mode 100644 test/support/resources/immutable_error_tester/validations/update_literal.ex create mode 100644 test/support/resources/immutable_error_tester/validations/update_many.ex create mode 100644 test/support/resources/immutable_error_tester/validations/update_one.ex diff --git a/lib/extensions/immutable_raise_error.ex b/lib/extensions/immutable_raise_error.ex index a1a4032c..3026602c 100644 --- a/lib/extensions/immutable_raise_error.ex +++ b/lib/extensions/immutable_raise_error.ex @@ -30,25 +30,37 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do ``` """ - use AshPostgres.CustomExtension, name: "immutable_raise_error", latest_version: 1 + use AshPostgres.CustomExtension, name: "immutable_raise_error", latest_version: 2 require Ecto.Query @impl true def install(0) do - ash_raise_error_immutable() + """ + #{ash_raise_error_immutable()} + + #{ash_to_jsonb_immutable()} + """ + end + + def install(1) do + ash_to_jsonb_immutable() end @impl true + def uninstall(2) do + "execute(\"DROP FUNCTION IF EXISTS ash_to_jsonb_immutable(anyelement)\")" + end + def uninstall(_version) do - "execute(\"DROP FUNCTION IF EXISTS ash_raise_error_immutable(jsonb, ANYCOMPATIBLE), ash_raise_error_immutable(jsonb, ANYELEMENT, ANYCOMPATIBLE)\")" + "execute(\"DROP FUNCTION IF EXISTS ash_to_jsonb_immutable(anyelement), ash_raise_error_immutable(jsonb, anycompatible), ash_raise_error_immutable(jsonb, anyelement, anycompatible)\")" end defp ash_raise_error_immutable do """ execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, token ANYCOMPATIBLE) - RETURNS BOOLEAN AS $$ + CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, token anycompatible) + RETURNS boolean AS $$ BEGIN -- Raise an error with the provided JSON data. -- The JSON object is converted to text for inclusion in the error message. @@ -62,8 +74,8 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do \"\"\") execute(\"\"\" - CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, type_signal ANYELEMENT, token ANYCOMPATIBLE) - RETURNS ANYELEMENT AS $$ + CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, type_signal anyelement, token anycompatible) + RETURNS anyelement AS $$ BEGIN -- Raise an error with the provided JSON data. -- The JSON object is converted to text for inclusion in the error message. @@ -78,60 +90,48 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do """ end + # Wraps to_jsonb and pins session GUCs that affect JSON. This makes the function’s result + # deterministic, so it is safe to mark IMMUTABLE. + defp ash_to_jsonb_immutable do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_to_jsonb_immutable(value anyelement) + RETURNS jsonb + LANGUAGE plpgsql + IMMUTABLE + SET search_path TO 'pg_catalog' + SET \"TimeZone\" TO 'UTC' + SET \"DateStyle\" TO 'ISO, YMD' + SET \"IntervalStyle\" TO 'iso_8601' + SET extra_float_digits TO '0' + SET bytea_output TO 'hex' + AS $function$ + BEGIN + RETURN COALESCE(to_jsonb(value), 'null'::jsonb); + END; + $function$ + \"\"\") + """ + end + @doc false def immutable_error_expr( query, %Ash.Query.Function.Error{arguments: [exception, input]} = value, bindings, - embedded?, + _embedded?, acc, type ) do + if !(Keyword.keyword?(input) or is_map(input)) do + raise "Input expression to `error` must be a map or keyword list" + end + acc = %{acc | has_error?: true} - {encoded, acc} = + {error_payload, acc} = if Ash.Expr.expr?(input) do - frag_parts = - Enum.flat_map(input, fn {key, value} -> - if Ash.Expr.expr?(value) do - [ - expr: to_string(key), - raw: "::text, ", - expr: value, - raw: ", " - ] - else - [ - expr: to_string(key), - raw: "::text, ", - expr: value, - raw: "::jsonb, " - ] - end - end) - - frag_parts = - List.update_at(frag_parts, -1, fn {:raw, text} -> - {:raw, String.trim_trailing(text, ", ") <> "))"} - end) - - AshSql.Expr.dynamic_expr( - query, - %Ash.Query.Function.Fragment{ - embedded?: false, - arguments: - [ - raw: "jsonb_build_object('exception', ", - expr: inspect(exception), - raw: "::text, 'input', jsonb_build_object(" - ] ++ - frag_parts - }, - bindings, - embedded?, - nil, - acc - ) + expression_error_payload(exception, input, query, bindings, acc) else {Jason.encode!(%{exception: inspect(exception), input: Map.new(input)}), acc} end @@ -163,7 +163,7 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do {nil, row_token} -> {:ok, Ecto.Query.dynamic( - fragment("ash_raise_error_immutable(?::jsonb, ?)", ^encoded, ^row_token) + fragment("ash_raise_error_immutable(?::jsonb, ?)", ^error_payload, ^row_token) ), acc} {dynamic_type, row_token} -> @@ -171,7 +171,7 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do Ecto.Query.dynamic( fragment( "ash_raise_error_immutable(?::jsonb, ?, ?)", - ^encoded, + ^error_payload, ^dynamic_type, ^row_token ) @@ -179,6 +179,55 @@ defmodule AshPostgres.Extensions.ImmutableRaiseError do end end + # Encodes an error payload as jsonb using only IMMUTABLE SQL functions. + # + # Strategy: + # * Split the 'input' into Ash expressions and literal values + # * Build the base json map with the exception name and literal input values + # * For each expression value, use nested calls to `jsonb_set` (IMMUTABLE) to add the value to + # 'input', converting each expression to jsonb using `ash_to_jsonb_immutable` (which pins + # session GUCs for deterministic encoding) + defp expression_error_payload(exception, input, query, bindings, acc) do + {expr_inputs, literal_inputs} = + Enum.split_with(input, fn {_key, value} -> Ash.Expr.expr?(value) end) + + base_json = %{exception: inspect(exception), input: Map.new(literal_inputs)} + + Enum.reduce(expr_inputs, {base_json, acc}, fn + {key, expr_value}, {current_payload, acc} -> + path_expr = %Ash.Query.Function.Type{ + arguments: [["input", to_string(key)], {:array, :string}, []] + } + + new_value_jsonb = + %Ash.Query.Function.Fragment{ + arguments: [raw: "ash_to_jsonb_immutable(", expr: expr_value, raw: ")"] + } + + {%Ecto.Query.DynamicExpr{} = new_payload, acc} = + AshSql.Expr.dynamic_expr( + query, + %Ash.Query.Function.Fragment{ + arguments: [ + raw: "jsonb_set(", + expr: current_payload, + raw: "::jsonb, ", + expr: path_expr, + raw: ", ", + expr: new_value_jsonb, + raw: "::jsonb, true)" + ] + }, + bindings, + false, + nil, + acc + ) + + {new_payload, acc} + end) + end + # Returns a row-dependent token to prevent constant-folding for immutable functions. defp immutable_error_expr_token(query, bindings) do resource = query.__ash_bindings__.resource diff --git a/priv/resource_snapshots/test_repo/extensions.json b/priv/resource_snapshots/test_repo/extensions.json index d1c5a122..35a36695 100644 --- a/priv/resource_snapshots/test_repo/extensions.json +++ b/priv/resource_snapshots/test_repo/extensions.json @@ -6,6 +6,7 @@ "pg_trgm", "citext", "demo-functions_v1", + "immutable_raise_error_v2", "ltree" ] } \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json b/priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json new file mode 100644 index 00000000..5917eec5 --- /dev/null +++ b/priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json @@ -0,0 +1,226 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "atom_value", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "string_value", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "integer_value", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "float_value", + "type": "float" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "boolean_value", + "type": "boolean" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "struct_value", + "type": "map" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "uuid_value", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "date_value", + "type": "date" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "time_value", + "type": "time" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "ci_string_value", + "type": "citext" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "naive_datetime_value", + "type": "naive_datetime" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "utc_datetime_value", + "type": "utc_datetime" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "timestamptz_value", + "type": "timestamptz" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "string_array_value", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "response_value", + "type": "integer" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "nullable_string_value", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "BB177B855B058F50CA20AFCF6F624072C1395BFDF3FB0B2F60BDC14A15A053F1", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "immutable_error_testers" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json.license b/priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json.license new file mode 100644 index 00000000..b0a44fab --- /dev/null +++ b/priv/resource_snapshots/test_repo/immutable_error_testers/20251015134240.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2019 ash_postgres contributors + +SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20251015134238_install_immutable_raise_error_v2_extension.exs b/priv/test_repo/migrations/20251015134238_install_immutable_raise_error_v2_extension.exs new file mode 100644 index 00000000..eb4a8d77 --- /dev/null +++ b/priv/test_repo/migrations/20251015134238_install_immutable_raise_error_v2_extension.exs @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.TestRepo.Migrations.InstallImmutableRaiseErrorV220251015134237 do + @moduledoc """ + Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, token anycompatible) + RETURNS boolean AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + -- 'token' is intentionally ignored; its presence makes the call non-constant at the call site. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql + IMMUTABLE + SET search_path = ''; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_raise_error_immutable(json_data jsonb, type_signal anyelement, token anycompatible) + RETURNS anyelement AS $$ + BEGIN + -- Raise an error with the provided JSON data. + -- The JSON object is converted to text for inclusion in the error message. + -- 'token' is intentionally ignored; its presence makes the call non-constant at the call site. + RAISE EXCEPTION 'ash_error: %', json_data::text; + RETURN NULL; + END; + $$ LANGUAGE plpgsql + IMMUTABLE + SET search_path = ''; + """) + + execute(""" + CREATE OR REPLACE FUNCTION ash_to_jsonb_immutable(value anyelement) + RETURNS jsonb + LANGUAGE plpgsql + IMMUTABLE + SET search_path TO 'pg_catalog' + SET "TimeZone" TO 'UTC' + SET "DateStyle" TO 'ISO, YMD' + SET "IntervalStyle" TO 'iso_8601' + SET extra_float_digits TO '0' + SET bytea_output TO 'hex' + AS $function$ + BEGIN + RETURN COALESCE(to_jsonb(value), 'null'::jsonb); + END; + $function$ + """) + end + + def down do + # Uncomment this if you actually want to uninstall the extensions + # when this migration is rolled back: + execute("DROP FUNCTION IF EXISTS ash_to_jsonb_immutable(anyelement)") + end +end diff --git a/priv/test_repo/migrations/20251015134240_migrate_resources63.exs b/priv/test_repo/migrations/20251015134240_migrate_resources63.exs new file mode 100644 index 00000000..0454f8a4 --- /dev/null +++ b/priv/test_repo/migrations/20251015134240_migrate_resources63.exs @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.TestRepo.Migrations.MigrateResources63 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 + create table(:immutable_error_testers, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:atom_value, :text, null: false) + add(:string_value, :text, null: false) + add(:integer_value, :bigint, null: false) + add(:float_value, :float, null: false) + add(:boolean_value, :boolean, null: false) + add(:struct_value, :map, null: false) + add(:uuid_value, :uuid, null: false) + add(:date_value, :date, null: false) + add(:time_value, :time, null: false) + add(:ci_string_value, :citext, null: false) + add(:naive_datetime_value, :naive_datetime, null: false) + add(:utc_datetime_value, :utc_datetime, null: false) + add(:timestamptz_value, :timestamptz, null: false) + add(:string_array_value, {:array, :text}, null: false) + add(:response_value, :integer, null: false) + add(:nullable_string_value, :text) + end + end + + def down do + drop(table(:immutable_error_testers)) + end +end diff --git a/test/immutable_raise_error_test.exs b/test/immutable_raise_error_test.exs new file mode 100644 index 00000000..076efeaf --- /dev/null +++ b/test/immutable_raise_error_test.exs @@ -0,0 +1,124 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.ImmutableRaiseErrorTest do + use AshPostgres.RepoCase, async: false + + alias AshPostgres.Test.ImmutableErrorTester + + require Ash.Query + + setup do + original = Application.get_env(:ash_postgres, :test_repo_use_immutable_errors?) + Application.put_env(:ash_postgres, :test_repo_use_immutable_errors?, true) + + on_exit(fn -> + if is_nil(original) do + Application.delete_env(:ash_postgres, :test_repo_use_immutable_errors?) + else + Application.put_env(:ash_postgres, :test_repo_use_immutable_errors?, original) + end + end) + + :ok + end + + describe "atomic error payloads" do + test "update_one returns InvalidAttribute error with expression value" do + tester = create_tester() + + # The :update_one validation builds an error with a single expression value and literal + # values (non-empty base input). + assert {:error, %Ash.Error.Invalid{errors: [error]}} = + tester + |> Ash.Changeset.for_update(:update_one, %{integer_value: 99}) + |> Ash.update() + + assert %Ash.Error.Changes.InvalidAttribute{} = error + assert error.field == :integer_value + assert error.value == 99 + end + + test "update_many returns custom error containing all expression values" do + tester = create_tester() + + # The :update_many validation builds an error that include many (all attributes) value + # expressions, and zero literal values (empty base input). + assert {:error, %Ash.Error.Invalid{errors: [error]}} = + tester + |> Ash.Changeset.for_update(:update_many, %{}) + |> Ash.update() + + assert %ImmutableErrorTester.Error{} = error + + assert error.atom_value == "initial_atom" + assert error.string_value == "initial string" + assert error.integer_value == 10 + assert error.float_value == 1.5 + assert error.boolean_value == true + + assert error.struct_value == %{ + "active?" => true, + "count" => 1, + "name" => "initial" + } + + assert error.uuid_value == "00000000-0000-0000-0000-000000000000" + assert error.date_value == "2024-01-01" + assert error.time_value == "12:00:00" + assert error.ci_string_value == "Initial String" + assert error.naive_datetime_value == "2024-01-01T12:00:00" + assert error.utc_datetime_value == "2024-01-01T00:01:00" + assert error.timestamptz_value == "2024-01-01T00:02:00+00:00" + assert error.string_array_value == ["one", "two"] + + # Native value for :awaiting is 0 + assert error.response_value == 0 + assert error.nullable_string_value == nil + end + + test "update_literal returns literal payload" do + tester = create_tester() + + # The :update_literal validation builds an error with only literal values, zero expression values. + assert {:error, %Ash.Error.Invalid{errors: [error]}} = + tester + |> Ash.Changeset.for_update(:update_literal, %{}) + |> Ash.update() + + assert error.string_value == "literal string" + assert error.integer_value == 123 + assert error.float_value == 9.99 + assert error.boolean_value == false + assert error.string_array_value == ["alpha", "beta"] + assert error.nullable_string_value == nil + end + end + + defp create_tester do + input = + %{ + atom_value: :initial_atom, + string_value: "initial string", + integer_value: 10, + float_value: 1.5, + boolean_value: true, + struct_value: ImmutableErrorTester.Struct.new!(name: "initial", count: 1, active?: true), + uuid_value: "00000000-0000-0000-0000-000000000000", + date_value: ~D[2024-01-01], + time_value: ~T[12:00:00], + ci_string_value: "Initial String", + naive_datetime_value: ~N[2024-01-01 12:00:00], + utc_datetime_value: ~U[2024-01-01 00:01:00.00Z], + timestamptz_value: ~U[2024-01-01 00:02:00.00Z], + string_array_value: ["one", "two"], + response_value: :awaiting, + nullable_string_value: nil + } + + ImmutableErrorTester + |> Ash.Changeset.for_create(:create, input) + |> Ash.create!() + end +end diff --git a/test/support/domain.ex b/test/support/domain.ex index 2fb81323..8e9a19d3 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -58,6 +58,7 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Chat) resource(AshPostgres.Test.Message) resource(AshPostgres.Test.RSVP) + resource(AshPostgres.Test.ImmutableErrorTester) end authorization do diff --git a/test/support/resources/immutable_error_tester/error.ex b/test/support/resources/immutable_error_tester/error.ex new file mode 100644 index 00000000..15dca8ee --- /dev/null +++ b/test/support/resources/immutable_error_tester/error.ex @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.ImmutableErrorTester.Error do + @moduledoc false + use Splode.Error, + class: :invalid, + fields: [ + :atom_value, + :string_value, + :integer_value, + :float_value, + :boolean_value, + :struct_value, + :uuid_value, + :date_value, + :time_value, + :ci_string_value, + :naive_datetime_value, + :utc_datetime_value, + :timestamptz_value, + :string_array_value, + :response_value, + :nullable_string_value + ] + + def message(_error) do + "Immutable Error" + end +end diff --git a/test/support/resources/immutable_error_tester/immutable_error_tester.ex b/test/support/resources/immutable_error_tester/immutable_error_tester.ex new file mode 100644 index 00000000..50ec1613 --- /dev/null +++ b/test/support/resources/immutable_error_tester/immutable_error_tester.ex @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.ImmutableErrorTester do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + require Ash.Expr + import Ash.Expr + + postgres do + table "immutable_error_testers" + repo(AshPostgres.TestRepo) + end + + attributes do + uuid_primary_key(:id) + + attribute(:atom_value, :atom, allow_nil?: false, public?: true) + attribute(:string_value, :string, allow_nil?: false, public?: true) + attribute(:integer_value, :integer, allow_nil?: false, public?: true) + attribute(:float_value, :float, allow_nil?: false, public?: true) + attribute(:boolean_value, :boolean, allow_nil?: false, public?: true) + + attribute(:struct_value, AshPostgres.Test.ImmutableErrorTester.Struct, + allow_nil?: false, + public?: true + ) + + attribute(:uuid_value, Ash.Type.UUID, allow_nil?: false, public?: true) + attribute(:date_value, :date, allow_nil?: false, public?: true) + attribute(:time_value, :time, allow_nil?: false, public?: true) + attribute(:ci_string_value, :ci_string, allow_nil?: false, public?: true) + attribute(:naive_datetime_value, :naive_datetime, allow_nil?: false, public?: true) + attribute(:utc_datetime_value, :utc_datetime, allow_nil?: false, public?: true) + attribute(:timestamptz_value, AshPostgres.Timestamptz, allow_nil?: false, public?: true) + attribute(:string_array_value, {:array, :string}, allow_nil?: false, public?: true) + attribute(:response_value, AshPostgres.Test.Types.Response, allow_nil?: false, public?: true) + attribute(:nullable_string_value, :string, public?: true) + end + + actions do + defaults([:read]) + + create :create do + accept(:*) + end + + update :update_one do + argument(:integer_value, :integer, allow_nil?: false) + change(atomic_update(:integer_value, expr(^arg(:integer_value)))) + validate(AshPostgres.Test.ImmutableErrorTester.Validations.UpdateOne) + end + + update :update_many do + accept(:*) + validate(AshPostgres.Test.ImmutableErrorTester.Validations.UpdateMany) + end + + update :update_literal do + validate(AshPostgres.Test.ImmutableErrorTester.Validations.UpdateLiteral) + end + end +end diff --git a/test/support/resources/immutable_error_tester/struct.ex b/test/support/resources/immutable_error_tester/struct.ex new file mode 100644 index 00000000..2b24494f --- /dev/null +++ b/test/support/resources/immutable_error_tester/struct.ex @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.ImmutableErrorTester.Struct do + @moduledoc false + use Ash.TypedStruct + + typed_struct do + field(:name, :string, allow_nil?: false) + field(:count, :integer, allow_nil?: false) + field(:active?, :boolean, allow_nil?: false) + end +end diff --git a/test/support/resources/immutable_error_tester/validations/update_literal.ex b/test/support/resources/immutable_error_tester/validations/update_literal.ex new file mode 100644 index 00000000..453ec37e --- /dev/null +++ b/test/support/resources/immutable_error_tester/validations/update_literal.ex @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.ImmutableErrorTester.Validations.UpdateLiteral do + @moduledoc false + use Ash.Resource.Validation + + import Ash.Expr + + @impl true + def init(opts), do: {:ok, opts} + + # Validation that always fails. Builds an error with only literal values, zero expression values. + # + # Use fragment with PG function to ensure the validation runs as part of the query. + @impl true + def atomic(_changeset, _opts, _context) do + [ + {:atomic, :*, expr(fragment("pg_column_size(?) != 0", ^ref(:id))), + expr( + error(AshPostgres.Test.ImmutableErrorTester.Error, + string_value: "literal string", + integer_value: 123, + float_value: 9.99, + boolean_value: false, + string_array_value: ["alpha", "beta"], + nullable_string_value: nil + ) + )} + ] + end +end diff --git a/test/support/resources/immutable_error_tester/validations/update_many.ex b/test/support/resources/immutable_error_tester/validations/update_many.ex new file mode 100644 index 00000000..e41959dc --- /dev/null +++ b/test/support/resources/immutable_error_tester/validations/update_many.ex @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.ImmutableErrorTester.Validations.UpdateMany do + @moduledoc false + use Ash.Resource.Validation + + import Ash.Expr + + @impl true + def init(opts), do: {:ok, opts} + + # Validation that always fails. Builds an error that include many (all attributes) value + # expressions, and zero literal values (empty base input). + # + # Use fragment with PG function to ensure the validation runs as part of the query. + @impl true + def atomic(_changeset, _opts, _context) do + [ + {:atomic, :*, expr(fragment("pg_column_size(?) != 0", ^ref(:id))), + expr( + error( + AshPostgres.Test.ImmutableErrorTester.Error, + atom_value: ^atomic_ref(:atom_value), + string_value: ^atomic_ref(:string_value), + integer_value: ^atomic_ref(:integer_value), + float_value: ^atomic_ref(:float_value), + boolean_value: ^atomic_ref(:boolean_value), + struct_value: ^atomic_ref(:struct_value), + uuid_value: ^atomic_ref(:uuid_value), + date_value: ^atomic_ref(:date_value), + time_value: ^atomic_ref(:time_value), + ci_string_value: ^atomic_ref(:ci_string_value), + naive_datetime_value: ^atomic_ref(:naive_datetime_value), + utc_datetime_value: ^atomic_ref(:utc_datetime_value), + timestamptz_value: ^atomic_ref(:timestamptz_value), + string_array_value: ^atomic_ref(:string_array_value), + response_value: ^atomic_ref(:response_value), + nullable_string_value: ^atomic_ref(:nullable_string_value) + ) + )} + ] + end +end diff --git a/test/support/resources/immutable_error_tester/validations/update_one.ex b/test/support/resources/immutable_error_tester/validations/update_one.ex new file mode 100644 index 00000000..a7351e56 --- /dev/null +++ b/test/support/resources/immutable_error_tester/validations/update_one.ex @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.ImmutableErrorTester.Validations.UpdateOne do + @moduledoc false + use Ash.Resource.Validation + + import Ash.Expr + + @impl true + def init(opts), do: {:ok, opts} + + # Validation that always fails. Builds an error with a single expression value and literal + # values (non-empty base input). + # + # Use fragment with PG function to ensure the validation runs as part of the query. + @impl true + def atomic(_changeset, _opts, _context) do + [ + {:atomic, [:integer_value, :id], expr(fragment("pg_column_size(?) != 0", ^ref(:id))), + expr( + error( + Ash.Error.Changes.InvalidAttribute, + field: :integer_value, + value: ^atomic_ref(:integer_value), + message: "integer_value failed validation" + ) + )} + ] + end +end diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index cf45c646..b21721e8 100644 --- a/test/support/test_repo.ex +++ b/test/support/test_repo.ex @@ -16,7 +16,15 @@ defmodule AshPostgres.TestRepo do def prefer_transaction_for_atomic_updates?, do: false def installed_extensions do - ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension, "ltree"] -- + [ + "ash-functions", + "uuid-ossp", + "pg_trgm", + "citext", + AshPostgres.TestCustomExtension, + AshPostgres.Extensions.ImmutableRaiseError, + "ltree" + ] -- Application.get_env(:ash_postgres, :no_extensions, []) end @@ -40,4 +48,8 @@ defmodule AshPostgres.TestRepo do |> Ash.read!() |> Enum.map(&"org_#{&1.id}") end + + def immutable_expr_error? do + Application.get_env(:ash_postgres, :test_repo_use_immutable_errors?, false) + end end From b2216c4b509137ca9c364ac5368e91f542827e7a Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sat, 18 Oct 2025 01:28:08 -0400 Subject: [PATCH 1208/1215] test: add tests for ash_sql fix for exists paths in calculations chore: update ash --- mix.lock | 2 +- test/calculation_test.exs | 21 +++++++++++++++++++++ test/support/resources/author.ex | 6 ++++++ test/support/resources/post.ex | 6 ++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 7b15d0ea..c988302d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.7.0", "711b9eb200f81e0a071e8fe52272fde27e3548a5f3d30589dfcf23eaf34b0d12", [:mix], [{:crux, "~> 0.1.0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2581e3e2ad21aff8157b25c0f58c884524bee8d06ae8dc94d38ef0b8ae67b2a3"}, + "ash": {:hex, :ash, "3.7.1", "abb55dee19e0959e529e52fe0622468825ae05400f535484919713e492d9a9e7", [:mix], [{:crux, "~> 0.1.0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4474ce9befe9862d1ed73cadf8a755e836c45a14a7b3b952d02e1a12f2b2e529"}, "ash_sql": {:hex, :ash_sql, "0.3.7", "80affa5446075d71deb157c67290685a84b392d723be766bfb684f58fe0143de", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ce4d974b8e784171c5a2a62593b3672b42dfd4888fa2239f01a6b32bad769038"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/calculation_test.exs b/test/calculation_test.exs index cdad5c18..5e3a2a68 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1121,4 +1121,25 @@ defmodule AshPostgres.CalculationTest do first_comment_by_author.created_at ) end + + test "nested calculation with parent() in exists works" do + author = + Author + |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) + |> Ash.create!() + + _post = + Post + |> Ash.Changeset.for_create(:create, %{title: "test", author_id: author.id}) + |> Ash.create!() + + result = + Post + |> Ash.Query.load(:author_has_post_with_title_matching_their_first_name) + |> Ash.read!() + |> List.first() + + # Should be false since post title doesn't match author first name + refute result.author_has_post_with_title_matching_their_first_name + end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 16ca5a8e..3530513c 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -181,6 +181,12 @@ defmodule AshPostgres.Test.Author do calculate(:has_posts, :boolean, expr(exists(posts, true == true))) calculate(:has_no_posts, :boolean, expr(has_posts == false)) + calculate( + :has_post_with_title_matching_first_name, + :boolean, + expr(exists(posts, title == parent(first_name))) + ) + calculate(:profile_description_calc, :string, expr(profile.description), allow_nil?: true) end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index c15b6b0e..a4f04f7d 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -924,6 +924,12 @@ defmodule AshPostgres.Test.Post do ) ) + calculate( + :author_has_post_with_title_matching_their_first_name, + :boolean, + expr(author.has_post_with_title_matching_first_name) + ) + calculate(:has_author, :boolean, expr(exists(author, true == true))) calculate(:has_comments, :boolean, expr(exists(comments, true == true))) From 449ae3ff8715c3bb5598ff19d844e1ac728e25b7 Mon Sep 17 00:00:00 2001 From: Torkild Gundersen Kjevik Date: Sun, 19 Oct 2025 05:17:31 +0200 Subject: [PATCH 1209/1215] test: typed struct arrays with storage_type :jsonb & {:array, map} (#640) --- .../test_repo/authors/20251018130654_dev.json | 133 ++++++++++++++++++ .../authors/20251018130654_dev.json.license | 3 + ...20251018130654_migrate_resources64_dev.exs | 27 ++++ test/storage_types_test.exs | 82 +++++++++++ test/support/resources/author.ex | 6 +- test/support/resources/identity.ex | 13 ++ test/support/resources/preference.ex | 13 ++ 7 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/authors/20251018130654_dev.json create mode 100644 priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license create mode 100644 priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs create mode 100644 test/support/resources/identity.ex create mode 100644 test/support/resources/preference.ex diff --git a/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json b/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json new file mode 100644 index 00000000..e14b635e --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json @@ -0,0 +1,133 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "first_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "last_name", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "bio", + "type": "map" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "bios", + "type": "jsonb" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "badges", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "settings", + "type": "jsonb" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "identities", + "type": "jsonb" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "preferences", + "type": [ + "array", + "map" + ] + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "55CFEDA4CB07DD89D2807B080A6F0E14A0370D7EA0C48E67C36ACAC568137D95", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "authors" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license b/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license new file mode 100644 index 00000000..b0a44fab --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2019 ash_postgres contributors + +SPDX-License-Identifier: MIT diff --git a/priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs b/priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs new file mode 100644 index 00000000..ba9e1baf --- /dev/null +++ b/priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.TestRepo.Migrations.MigrateResources64 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(:authors) do + add(:identities, :jsonb) + add(:preferences, {:array, :map}) + end + end + + def down do + alter table(:authors) do + remove(:preferences) + remove(:identities) + end + end +end diff --git a/test/storage_types_test.exs b/test/storage_types_test.exs index ad414bf8..0f818048 100644 --- a/test/storage_types_test.exs +++ b/test/storage_types_test.exs @@ -93,4 +93,86 @@ defmodule AshPostgres.StorageTypesTest do |> Ash.Query.filter(not is_nil(settings["dues_reminders"])) |> Ash.read!() end + + test "can bulk update {:array, CustomTypedStruct} stored as jsonb" do + %{id: id} = + Author + |> Ash.Changeset.for_create( + :create, + %{ + first_name: "Test", + last_name: "User", + identities: [%{provider: "github", uid: "123"}, %{provider: "google", uid: "456"}] + } + ) + |> Ash.create!() + + %BulkResult{records: [author]} = + Author + |> Ash.Query.filter(id == ^id) + |> Ash.bulk_update(:update, %{identities: [%{provider: "gitlab", uid: "789"}]}, + return_errors?: true, + notify?: true, + strategy: [:atomic, :stream, :atomic_batches], + allow_stream_with: :full_read, + return_records?: true + ) + + assert length(author.identities) == 1 + assert %{provider: "gitlab", uid: "789"} = hd(author.identities) + + %BulkResult{records: [author]} = + Author + |> Ash.Query.filter(id == ^id) + |> Ash.bulk_update(:update, %{identities: []}, + return_errors?: true, + notify?: true, + strategy: [:atomic, :stream, :atomic_batches], + allow_stream_with: :full_read, + return_records?: true + ) + + assert author.identities == [] + end + + test "can bulk update {:array, CustomTypedStruct} stored as {:array, :map}" do + %{id: id} = + Author + |> Ash.Changeset.for_create( + :create, + %{ + first_name: "Test", + last_name: "User", + preferences: [%{key: "theme", value: "dark"}, %{key: "lang", value: "en"}] + } + ) + |> Ash.create!() + + %BulkResult{records: [author]} = + Author + |> Ash.Query.filter(id == ^id) + |> Ash.bulk_update(:update, %{preferences: [%{key: "theme", value: "light"}]}, + return_errors?: true, + notify?: true, + strategy: [:atomic, :stream, :atomic_batches], + allow_stream_with: :full_read, + return_records?: true + ) + + assert length(author.preferences) == 1 + assert %{key: "theme", value: "light"} = hd(author.preferences) + + %BulkResult{records: [author]} = + Author + |> Ash.Query.filter(id == ^id) + |> Ash.bulk_update(:update, %{preferences: []}, + return_errors?: true, + notify?: true, + strategy: [:atomic, :stream, :atomic_batches], + allow_stream_with: :full_read, + return_records?: true + ) + + assert author.preferences == [] + end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index 3530513c..4203b262 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -23,8 +23,8 @@ defmodule AshPostgres.Test.Author do table("authors") repo(AshPostgres.TestRepo) - migration_types bios: :jsonb, settings: :jsonb - storage_types(bios: :jsonb, settings: :jsonb) + migration_types bios: :jsonb, settings: :jsonb, identities: :jsonb + storage_types(bios: :jsonb, settings: :jsonb, identities: :jsonb) end attributes do @@ -35,6 +35,8 @@ defmodule AshPostgres.Test.Author do attribute(:bios, {:array, :map}, public?: true) attribute(:badges, {:array, :atom}, public?: true) attribute(:settings, AshPostgres.Test.Settings, public?: true) + attribute(:identities, {:array, AshPostgres.Test.Identity}, public?: true) + attribute(:preferences, {:array, AshPostgres.Test.Preference}, public?: true) end actions do diff --git a/test/support/resources/identity.ex b/test/support/resources/identity.ex new file mode 100644 index 00000000..d29aaa07 --- /dev/null +++ b/test/support/resources/identity.ex @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.Identity do + @moduledoc false + use Ash.TypedStruct + + typed_struct do + field(:provider, :string, allow_nil?: false) + field(:uid, :string, allow_nil?: false) + end +end diff --git a/test/support/resources/preference.ex b/test/support/resources/preference.ex new file mode 100644 index 00000000..b5e707fa --- /dev/null +++ b/test/support/resources/preference.ex @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.Preference do + @moduledoc false + use Ash.TypedStruct + + typed_struct do + field(:key, :string, allow_nil?: false) + field(:value, :string, allow_nil?: false) + end +end From 1023dedf41af0f9772240c42f7cf369b1b29b5c1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 19 Oct 2025 12:27:55 -0400 Subject: [PATCH 1210/1215] improvement: remove unused bulk operation metadata function & update ash --- lib/data_layer.ex | 52 +++++++++++------------------------------------ mix.exs | 2 +- mix.lock | 6 +++--- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index b984c4db..4469f369 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2138,18 +2138,12 @@ defmodule AshPostgres.DataLayer do maybe_create_tenant!(resource, result_for_changeset) end - case get_bulk_operation_metadata(changeset) do - {index, metadata_key} -> - Ash.Resource.put_metadata(result_for_changeset, metadata_key, index) - - nil -> - # Compatibility fallback - Ash.Resource.put_metadata( - result_for_changeset, - :bulk_create_index, - changeset.context[:bulk_create][:index] - ) - end + # Compatibility fallback + Ash.Resource.put_metadata( + result_for_changeset, + :bulk_create_index, + changeset.context[:bulk_create][:index] + ) end end) |> Enum.filter(& &1) @@ -2161,18 +2155,12 @@ defmodule AshPostgres.DataLayer do maybe_create_tenant!(resource, result) end - case get_bulk_operation_metadata(changeset) do - {index, metadata_key} -> - Ash.Resource.put_metadata(result, metadata_key, index) - - nil -> - # Compatibility fallback - Ash.Resource.put_metadata( - result, - :bulk_create_index, - changeset.context[:bulk_create][:index] - ) - end + # Compatibility fallback + Ash.Resource.put_metadata( + result, + :bulk_create_index, + changeset.context[:bulk_create][:index] + ) end) end @@ -3759,20 +3747,4 @@ defmodule AshPostgres.DataLayer do resource end end - - defp get_bulk_operation_metadata(changeset) do - changeset.context - |> Enum.find_value(fn - # New format: {{:bulk_create, ref}, value} -> {index, metadata_key} - {{:bulk_create, ref}, value} -> - {value.index, {:bulk_create_index, ref}} - - # Fallback for old format: {:bulk_create, value} -> {index, metadata_key} - {:bulk_create, value} when is_map(value) -> - {value.index, :bulk_create_index} - - _ -> - nil - end) - end end diff --git a/mix.exs b/mix.exs index 5e14da8d..526064c2 100644 --- a/mix.exs +++ b/mix.exs @@ -177,7 +177,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.7")}, + {:ash, ash_version("~> 3.7 and >= 3.7.5")}, {:spark, "~> 2.3 and >= 2.3.4"}, {:ash_sql, ash_sql_version("~> 0.3 and >= 0.3.7")}, {:igniter, "~> 0.6 and >= 0.6.29", optional: true}, diff --git a/mix.lock b/mix.lock index c988302d..a40b623c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,10 @@ %{ - "ash": {:hex, :ash, "3.7.1", "abb55dee19e0959e529e52fe0622468825ae05400f535484919713e492d9a9e7", [:mix], [{:crux, "~> 0.1.0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4474ce9befe9862d1ed73cadf8a755e836c45a14a7b3b952d02e1a12f2b2e529"}, + "ash": {:hex, :ash, "3.7.5", "91e926d14f51a6b406660bd136c3165316d37f2ce68f0ca6118d7c0e41174f0c", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df839ac2321768dde1216e317e5a824a136251699f8837a1e8a9ee4508f7e47a"}, "ash_sql": {:hex, :ash_sql, "0.3.7", "80affa5446075d71deb157c67290685a84b392d723be766bfb684f58fe0143de", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ce4d974b8e784171c5a2a62593b3672b42dfd4888fa2239f01a6b32bad769038"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, - "crux": {:hex, :crux, "0.1.1", "94f2f97d2a6079ae3c57f356412bc3b307f9579a80e43f526447b1d508dd4a72", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "e59d498f038193cbe31e448f9199f5b4c53a4c67cece9922bb839595189dd2b6"}, + "credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"}, + "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, From 89ca1fe164cb9c24ac1ff822ec6891d23a5bf91f Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 19 Oct 2025 12:30:18 -0400 Subject: [PATCH 1211/1215] chore: update mix.lock --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index a40b623c..9b2bc487 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "ash": {:hex, :ash, "3.7.5", "91e926d14f51a6b406660bd136c3165316d37f2ce68f0ca6118d7c0e41174f0c", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df839ac2321768dde1216e317e5a824a136251699f8837a1e8a9ee4508f7e47a"}, - "ash_sql": {:hex, :ash_sql, "0.3.7", "80affa5446075d71deb157c67290685a84b392d723be766bfb684f58fe0143de", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "ce4d974b8e784171c5a2a62593b3672b42dfd4888fa2239f01a6b32bad769038"}, + "ash_sql": {:hex, :ash_sql, "0.3.8", "9f55866149b4fc092eb37c346e0734143f70dbfae8793c86a5a46803c2b47a12", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0b58a8191744347bb349ace9affb3effc5ceaf8f1bc572915a5cf2ec4c45b72d"}, "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"}, From 913f9360a5c590c56ed80b984d2f434133ff773c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 19 Oct 2025 22:41:24 -0400 Subject: [PATCH 1212/1215] chore: fix tests & don't do normal rollbacks with `--tenant` option --- lib/data_layer.ex | 130 +++++++++--------- .../migration_generator.ex | 14 +- .../authors/20251018130654_dev.json.license | 3 - ...018130654_dev.json => 20251020024026.json} | 2 +- ...=> 20251020024026_migrate_resources64.exs} | 4 - 5 files changed, 69 insertions(+), 84 deletions(-) delete mode 100644 priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license rename priv/resource_snapshots/test_repo/authors/{20251018130654_dev.json => 20251020024026.json} (97%) rename priv/test_repo/migrations/{20251018130654_migrate_resources64_dev.exs => 20251020024026_migrate_resources64.exs} (75%) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index 4469f369..910e0e87 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -453,72 +453,74 @@ defmodule AshPostgres.DataLayer do migrations_path = AshPostgres.Mix.Helpers.migrations_path([], repo) tenant_migrations_path = AshPostgres.Mix.Helpers.tenant_migrations_path([], repo) - current_migrations = - Ecto.Query.from(row in "schema_migrations", - select: row.version - ) - |> repo.all() - |> Enum.map(&to_string/1) + if "--tenants" not in args do + current_migrations = + Ecto.Query.from(row in "schema_migrations", + select: row.version + ) + |> repo.all() + |> Enum.map(&to_string/1) + + files = + migrations_path + |> Path.join("**/*.exs") + |> Path.wildcard() + |> Enum.sort() + |> Enum.reverse() + |> Enum.filter(fn file -> + Enum.any?(current_migrations, &String.starts_with?(Path.basename(file), &1)) + end) + |> Enum.take(20) + |> Enum.map(&String.trim_leading(&1, migrations_path)) + |> Enum.map(&String.trim_leading(&1, "/")) + + indexed = + files + |> Enum.with_index() + |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) + + to = + Mix.shell().prompt( + """ + How many migrations should be rolled back#{for_repo}? (default: 0) + + Last 20 migration names, with the input you must provide to + rollback up to *and including* that migration: + + #{Enum.join(indexed, "\n")} + Rollback to: + """ + |> String.trim_trailing() + ) + |> String.trim() + |> case do + "" -> + nil - files = - migrations_path - |> Path.join("**/*.exs") - |> Path.wildcard() - |> Enum.sort() - |> Enum.reverse() - |> Enum.filter(fn file -> - Enum.any?(current_migrations, &String.starts_with?(Path.basename(file), &1)) - end) - |> Enum.take(20) - |> Enum.map(&String.trim_leading(&1, migrations_path)) - |> Enum.map(&String.trim_leading(&1, "/")) - - indexed = - files - |> Enum.with_index() - |> Enum.map(fn {file, index} -> "#{index + 1}: #{file}" end) - - to = - Mix.shell().prompt( - """ - How many migrations should be rolled back#{for_repo}? (default: 0) - - Last 20 migration names, with the input you must provide to - rollback up to *and including* that migration: - - #{Enum.join(indexed, "\n")} - Rollback to: - """ - |> String.trim_trailing() - ) - |> String.trim() - |> case do - "" -> - nil - - "0" -> - nil - - n -> - try do - files - |> Enum.at(String.to_integer(n) - 1) - rescue - _ -> - reraise "Required an integer value, got: #{n}", __STACKTRACE__ - end - |> String.split("_", parts: 2) - |> Enum.at(0) - |> String.to_integer() - end + "0" -> + nil - if to do - Mix.Task.run( - "ash_postgres.rollback", - args ++ ["-r", inspect(repo), "--to", to_string(to)] - ) + n -> + try do + files + |> Enum.at(String.to_integer(n) - 1) + rescue + _ -> + reraise "Required an integer value, got: #{n}", __STACKTRACE__ + end + |> String.split("_", parts: 2) + |> Enum.at(0) + |> String.to_integer() + end - Mix.Task.reenable("ash_postgres.rollback") + if to do + Mix.Task.run( + "ash_postgres.rollback", + args ++ ["-r", inspect(repo), "--to", to_string(to)] + ) + + Mix.Task.reenable("ash_postgres.rollback") + end end tenant_files = @@ -599,7 +601,7 @@ defmodule AshPostgres.DataLayer do if to do Mix.Task.run( "ash_postgres.rollback", - args ++ ["--tenants", "-r", inspect(repo), "--to", to] + args ++ ["--tenants", "-r", inspect(repo), "--to", to_string(to)] ) Mix.Task.reenable("ash_postgres.rollback") diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index 9881818c..425ccb53 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -514,16 +514,6 @@ defmodule AshPostgres.MigrationGenerator do end end - if Mix.env() == :test do - defp with_repo_not_in_test(repo, fun) do - fun.(repo) - end - else - defp with_repo_not_in_test(repo, fun) do - Ecto.Migrator.with_repo(repo, fun) - end - end - defp require_name!(opts) do if !opts.name && !opts.dry_run && !opts.check && !opts.snapshots_only && !opts.dev && !opts.auto_name do @@ -548,7 +538,7 @@ defmodule AshPostgres.MigrationGenerator do end) if tenant? do - with_repo_not_in_test(repo, fn repo -> + Ecto.Migrator.with_repo(repo, fn repo -> for prefix <- repo.all_tenants() do {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], prefix) @@ -583,7 +573,7 @@ defmodule AshPostgres.MigrationGenerator do end end) else - with_repo_not_in_test(repo, fn repo -> + Ecto.Migrator.with_repo(repo, fn repo -> {repo, query, opts} = Ecto.Migration.SchemaMigration.versions(repo, [], nil) repo.transaction(fn -> diff --git a/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license b/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license deleted file mode 100644 index b0a44fab..00000000 --- a/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2019 ash_postgres contributors - -SPDX-License-Identifier: MIT diff --git a/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json b/priv/resource_snapshots/test_repo/authors/20251020024026.json similarity index 97% rename from priv/resource_snapshots/test_repo/authors/20251018130654_dev.json rename to priv/resource_snapshots/test_repo/authors/20251020024026.json index e14b635e..761a9e2a 100644 --- a/priv/resource_snapshots/test_repo/authors/20251018130654_dev.json +++ b/priv/resource_snapshots/test_repo/authors/20251020024026.json @@ -120,7 +120,7 @@ "custom_indexes": [], "custom_statements": [], "has_create_action": true, - "hash": "55CFEDA4CB07DD89D2807B080A6F0E14A0370D7EA0C48E67C36ACAC568137D95", + "hash": "DEDCEFDB71DE94818B8D624B0BFE438AD7AEDC6279FD05B83C11C216E7A0F991", "identities": [], "multitenancy": { "attribute": null, diff --git a/priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs b/priv/test_repo/migrations/20251020024026_migrate_resources64.exs similarity index 75% rename from priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs rename to priv/test_repo/migrations/20251020024026_migrate_resources64.exs index ba9e1baf..ff7d63f3 100644 --- a/priv/test_repo/migrations/20251018130654_migrate_resources64_dev.exs +++ b/priv/test_repo/migrations/20251020024026_migrate_resources64.exs @@ -1,7 +1,3 @@ -# SPDX-FileCopyrightText: 2019 ash_postgres contributors -# -# SPDX-License-Identifier: MIT - defmodule AshPostgres.TestRepo.Migrations.MigrateResources64 do @moduledoc """ Updates resources based on their most recent snapshots. From 7b67d31d64eccb237305a6b07154754e934dc2b1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Oct 2025 20:29:32 -0400 Subject: [PATCH 1213/1215] chore: update spark --- mix.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.lock b/mix.lock index 9b2bc487..46883a7b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ - "ash": {:hex, :ash, "3.7.5", "91e926d14f51a6b406660bd136c3165316d37f2ce68f0ca6118d7c0e41174f0c", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df839ac2321768dde1216e317e5a824a136251699f8837a1e8a9ee4508f7e47a"}, - "ash_sql": {:hex, :ash_sql, "0.3.8", "9f55866149b4fc092eb37c346e0734143f70dbfae8793c86a5a46803c2b47a12", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "0b58a8191744347bb349ace9affb3effc5ceaf8f1bc572915a5cf2ec4c45b72d"}, - "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, + "ash": {:hex, :ash, "3.7.6", "a0358e8467da4e2a94855542d07d7fca8e74cb6bc89c42af2181b4caa91f8415", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6003aa4dec5868e6371c3bf2efdb89507c59c05f5dbec13a13b73a92b938a258"}, + "ash_sql": {:hex, :ash_sql, "0.3.10", "f789d87b797a86939245436c68a7bab23388c78063aa0d3b57f00e56b171f9f0", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f381d2ccaa86057e559d54b7207fef967ecd5ee8053b7053d4e09e5bdbe55176"}, + "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"}, "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, @@ -10,14 +10,14 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, + "ecto": {:hex, :ecto, "3.13.4", "27834b45d58075d4a414833d9581e8b7bb18a8d9f264a21e42f653d500dbeeb5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ad7d1505685dfa7aaf86b133d54f5ad6c42df0b4553741a1ff48796736e88b2"}, "ecto_dev_logger": {:hex, :ecto_dev_logger, "0.15.0", "df5a997ffb17dca9011556857a0f5b7d8cd53ca7c452ef98828664b6e48d4400", [:mix], [{:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:geo, "~> 3.5 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "b2c807d7d599a4fcf288139851c09262333b193bdb41f8d65f515853d117e88a"}, "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, "eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, - "ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"}, + "ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, @@ -45,7 +45,7 @@ "simple_sat": {:hex, :simple_sat, "0.1.4", "39baf72cdca14f93c0b6ce2b6418b72bbb67da98fa9ca4384e2f79bbc299899d", [:mix], [], "hexpm", "3569b68e346a5fd7154b8d14173ff8bcc829f2eb7b088c30c3f42a383443930b"}, "sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, - "spark": {:hex, :spark, "2.3.5", "f30d30ecc3b4ab9b932d9aada66af7677fc1f297a2c349b0bcec3eafb9f996e8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0e9d339704d5d148f77f2b2fef3bcfc873a9e9bb4224fcf289c545d65827202f"}, + "spark": {:hex, :spark, "2.3.12", "55f597df09cd38944c888f00e12f8b1f1fd94b0b4ed76a199e1d1d8251d9220a", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "4f69b30cab6ac72e6f16e0f6b4f815d3ce3915628612f38059dcea4a25b53fe0"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, From defb1db573df236f31bf6300642c7a809c4d17bd Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Oct 2025 20:40:59 -0400 Subject: [PATCH 1214/1215] chore: release version v2.6.24 --- CHANGELOG.md | 15 +++++++++++++++ mix.exs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd50c84d..fa148895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,21 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide +## [v2.6.24](https://github.com/ash-project/ash_postgres/compare/v2.6.23...v2.6.24) (2025-10-30) + + + + +### Bug Fixes: + +* handle results that can't be mapped to the changeset in bulk_create (#638) by Barnabas Jovanovics + +* handle results that can't be mapped to the changeset in bulk_create by Barnabas Jovanovics + +### Improvements: + +* remove unused bulk operation metadata function & update ash by Zach Daniel + ## [v2.6.23](https://github.com/ash-project/ash_postgres/compare/v2.6.22...v2.6.23) (2025-10-15) diff --git a/mix.exs b/mix.exs index 526064c2..3d6566f5 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,7 @@ defmodule AshPostgres.MixProject do The PostgreSQL data layer for Ash Framework """ - @version "2.6.23" + @version "2.6.24" def project do [ From 277c0557f189346d2ecf4e165836b0fb5cb800ae Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 29 Oct 2025 23:45:08 -0400 Subject: [PATCH 1215/1215] fix: add failing test for exists expansnion inside of calculations --- .../food_categories/20251030033449.json | 43 ++++++++ .../test_repo/food_items/20251030033449.json | 74 ++++++++++++++ .../test_repo/meal_items/20251030033449.json | 62 ++++++++++++ .../test_repo/meal_items/20251030033946.json | 93 +++++++++++++++++ .../test_repo/meals/20251030033946.json | 43 ++++++++ .../20251030033449.json | 74 ++++++++++++++ .../20251030033449_migrate_resources65.exs | 93 +++++++++++++++++ .../20251030033946_migrate_resources66.exs | 39 ++++++++ test/calculation_test.exs | 99 +++++++++++++++++++ test/support/domain.ex | 5 + test/support/resources/food_category.ex | 29 ++++++ test/support/resources/food_item.ex | 60 +++++++++++ test/support/resources/meal.ex | 50 ++++++++++ test/support/resources/meal_item.ex | 58 +++++++++++ .../resources/user_excluded_category.ex | 41 ++++++++ 15 files changed, 863 insertions(+) create mode 100644 priv/resource_snapshots/test_repo/food_categories/20251030033449.json create mode 100644 priv/resource_snapshots/test_repo/food_items/20251030033449.json create mode 100644 priv/resource_snapshots/test_repo/meal_items/20251030033449.json create mode 100644 priv/resource_snapshots/test_repo/meal_items/20251030033946.json create mode 100644 priv/resource_snapshots/test_repo/meals/20251030033946.json create mode 100644 priv/resource_snapshots/test_repo/user_excluded_categories/20251030033449.json create mode 100644 priv/test_repo/migrations/20251030033449_migrate_resources65.exs create mode 100644 priv/test_repo/migrations/20251030033946_migrate_resources66.exs create mode 100644 test/support/resources/food_category.ex create mode 100644 test/support/resources/food_item.ex create mode 100644 test/support/resources/meal.ex create mode 100644 test/support/resources/meal_item.ex create mode 100644 test/support/resources/user_excluded_category.ex diff --git a/priv/resource_snapshots/test_repo/food_categories/20251030033449.json b/priv/resource_snapshots/test_repo/food_categories/20251030033449.json new file mode 100644 index 00000000..cca7a0d7 --- /dev/null +++ b/priv/resource_snapshots/test_repo/food_categories/20251030033449.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "C0AD1AD07773C89729452B795DC3C5AA65F036F0F7329CC508BE4C8F639E1E9A", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "food_categories" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/food_items/20251030033449.json b/priv/resource_snapshots/test_repo/food_items/20251030033449.json new file mode 100644 index 00000000..e4519bc6 --- /dev/null +++ b/priv/resource_snapshots/test_repo/food_items/20251030033449.json @@ -0,0 +1,74 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "food_items_food_category_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "food_categories" + }, + "scale": null, + "size": null, + "source": "food_category_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "A764D687C75A1542B034A45668E7739BB8C80D19D8B7688D528ADF04E2599202", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "food_items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/meal_items/20251030033449.json b/priv/resource_snapshots/test_repo/meal_items/20251030033449.json new file mode 100644 index 00000000..8581505f --- /dev/null +++ b/priv/resource_snapshots/test_repo/meal_items/20251030033449.json @@ -0,0 +1,62 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "meal_items_food_item_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "food_items" + }, + "scale": null, + "size": null, + "source": "food_item_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "7FD8D8743BF6F640231EB321F8DC3BD5095ADCBFBB51D643A9A5D0AAA2EDE89B", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "meal_items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/meal_items/20251030033946.json b/priv/resource_snapshots/test_repo/meal_items/20251030033946.json new file mode 100644 index 00000000..79ffdabd --- /dev/null +++ b/priv/resource_snapshots/test_repo/meal_items/20251030033946.json @@ -0,0 +1,93 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "meal_items_meal_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "meals" + }, + "scale": null, + "size": null, + "source": "meal_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "meal_items_food_item_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "food_items" + }, + "scale": null, + "size": null, + "source": "food_item_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "80EEF038E8EFD6A4BCBFDDB9383579B103A4AAC26E8B11C8787578F336EDF306", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "meal_items" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/meals/20251030033946.json b/priv/resource_snapshots/test_repo/meals/20251030033946.json new file mode 100644 index 00000000..f514fd21 --- /dev/null +++ b/priv/resource_snapshots/test_repo/meals/20251030033946.json @@ -0,0 +1,43 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "name", + "type": "text" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "5EC21805FBF1D973E60F8D0E19EE83CF145661BFB4FA01AC0C5D8979A52F995A", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "meals" +} \ No newline at end of file diff --git a/priv/resource_snapshots/test_repo/user_excluded_categories/20251030033449.json b/priv/resource_snapshots/test_repo/user_excluded_categories/20251030033449.json new file mode 100644 index 00000000..ca1f4bea --- /dev/null +++ b/priv/resource_snapshots/test_repo/user_excluded_categories/20251030033449.json @@ -0,0 +1,74 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "precision": null, + "primary_key?": true, + "references": null, + "scale": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "user_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "user_excluded_categories_food_category_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "food_categories" + }, + "scale": null, + "size": null, + "source": "food_category_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "091CE9C80EE765F7A6FED9FD201B2B48E9564FD9C4AB094BB0C7DC566368F570", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "schema": null, + "table": "user_excluded_categories" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20251030033449_migrate_resources65.exs b/priv/test_repo/migrations/20251030033449_migrate_resources65.exs new file mode 100644 index 00000000..e87bf47b --- /dev/null +++ b/priv/test_repo/migrations/20251030033449_migrate_resources65.exs @@ -0,0 +1,93 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources65 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 + create table(:food_categories, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text, null: false) + end + + create table(:meal_items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:food_item_id, :uuid, null: false) + end + + create table(:user_excluded_categories, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:user_id, :uuid, null: false) + + add( + :food_category_id, + references(:food_categories, + column: :id, + name: "user_excluded_categories_food_category_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + ) + end + + create table(:food_items, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + end + + alter table(:meal_items) do + modify( + :food_item_id, + references(:food_items, + column: :id, + name: "meal_items_food_item_id_fkey", + type: :uuid, + prefix: "public" + ) + ) + end + + alter table(:food_items) do + add(:name, :text, null: false) + + add( + :food_category_id, + references(:food_categories, + column: :id, + name: "food_items_food_category_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + ) + end + end + + def down do + drop(constraint(:food_items, "food_items_food_category_id_fkey")) + + alter table(:food_items) do + remove(:food_category_id) + remove(:name) + end + + drop(constraint(:meal_items, "meal_items_food_item_id_fkey")) + + alter table(:meal_items) do + modify(:food_item_id, :uuid) + end + + drop(table(:food_items)) + + drop(constraint(:user_excluded_categories, "user_excluded_categories_food_category_id_fkey")) + + drop(table(:user_excluded_categories)) + + drop(table(:meal_items)) + + drop(table(:food_categories)) + end +end diff --git a/priv/test_repo/migrations/20251030033946_migrate_resources66.exs b/priv/test_repo/migrations/20251030033946_migrate_resources66.exs new file mode 100644 index 00000000..04ee4a7b --- /dev/null +++ b/priv/test_repo/migrations/20251030033946_migrate_resources66.exs @@ -0,0 +1,39 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources66 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 + create table(:meals, primary_key: false) do + add(:id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true) + add(:name, :text, null: false) + end + + alter table(:meal_items) do + add( + :meal_id, + references(:meals, + column: :id, + name: "meal_items_meal_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + ) + end + end + + def down do + drop(constraint(:meal_items, "meal_items_meal_id_fkey")) + + alter table(:meal_items) do + remove(:meal_id) + end + + drop(table(:meals)) + end +end diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 5e3a2a68..464df4e0 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -1142,4 +1142,103 @@ defmodule AshPostgres.CalculationTest do # Should be false since post title doesn't match author first name refute result.author_has_post_with_title_matching_their_first_name end + + test "nested calculation with parent() and arguments in exists works" do + user_id = Ash.UUID.generate() + + # Create a food category + category = + AshPostgres.Test.FoodCategory + |> Ash.Changeset.for_create(:create, %{name: "Dairy"}) + |> Ash.create!() + + # Create a food item in that category + food_item = + AshPostgres.Test.FoodItem + |> Ash.Changeset.for_create(:create, %{ + name: "Cheese", + food_category_id: category.id + }) + |> Ash.create!() + + # Create a meal + meal = + AshPostgres.Test.Meal + |> Ash.Changeset.for_create(:create, %{name: "Breakfast"}) + |> Ash.create!() + + # Create a meal item with that food item + AshPostgres.Test.MealItem + |> Ash.Changeset.for_create(:create, %{meal_id: meal.id, food_item_id: food_item.id}) + |> Ash.create!() + + # User has not excluded any categories, so meal should be allowed + result = + AshPostgres.Test.Meal + |> Ash.Query.load(allowed_for_user: %{user_id: user_id}) + |> Ash.read_one!() + + assert result.allowed_for_user == true + + # Now exclude the category for the user + AshPostgres.Test.UserExcludedCategory + |> Ash.Changeset.for_create(:create, %{ + user_id: user_id, + food_category_id: category.id + }) + |> Ash.create!() + + # Now the meal should not be allowed for the user (because it contains an excluded food) + result = + AshPostgres.Test.Meal + |> Ash.Query.load(allowed_for_user: %{user_id: user_id}) + |> Ash.read_one!() + + refute result.allowed_for_user + end + + test "can filter on nested calculation with parent() and arguments in exists" do + user_id = Ash.UUID.generate() + + # Create a food category + category = + AshPostgres.Test.FoodCategory + |> Ash.Changeset.for_create(:create, %{name: "Dairy"}) + |> Ash.create!() + + # Create a food item in that category + food_item = + AshPostgres.Test.FoodItem + |> Ash.Changeset.for_create(:create, %{ + name: "Cheese", + food_category_id: category.id + }) + |> Ash.create!() + + # Create a meal + meal = + AshPostgres.Test.Meal + |> Ash.Changeset.for_create(:create, %{name: "Breakfast"}) + |> Ash.create!() + + # Create a meal item with that food item + AshPostgres.Test.MealItem + |> Ash.Changeset.for_create(:create, %{meal_id: meal.id, food_item_id: food_item.id}) + |> Ash.create!() + + # Exclude the category for the user + AshPostgres.Test.UserExcludedCategory + |> Ash.Changeset.for_create(:create, %{ + user_id: user_id, + food_category_id: category.id + }) + |> Ash.create!() + + # Filter MealItems by the calculation - this should trigger the parent() binding issue + query = + AshPostgres.Test.MealItem + |> Ash.Query.filter(allowed_for_user(user_id: ^user_id)) + + assert [] == Ash.read!(query) + end end diff --git a/test/support/domain.ex b/test/support/domain.ex index 8e9a19d3..ea60cb5f 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -59,6 +59,11 @@ defmodule AshPostgres.Test.Domain do resource(AshPostgres.Test.Message) resource(AshPostgres.Test.RSVP) resource(AshPostgres.Test.ImmutableErrorTester) + resource(AshPostgres.Test.FoodCategory) + resource(AshPostgres.Test.UserExcludedCategory) + resource(AshPostgres.Test.FoodItem) + resource(AshPostgres.Test.Meal) + resource(AshPostgres.Test.MealItem) end authorization do diff --git a/test/support/resources/food_category.ex b/test/support/resources/food_category.ex new file mode 100644 index 00000000..568ec0fa --- /dev/null +++ b/test/support/resources/food_category.ex @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.FoodCategory do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "food_categories" + repo AshPostgres.TestRepo + end + + actions do + default_accept :* + defaults [:read, :destroy, create: :*, update: :*] + end + + attributes do + uuid_primary_key :id + + attribute :name, :string do + public? true + allow_nil? false + end + end +end diff --git a/test/support/resources/food_item.ex b/test/support/resources/food_item.ex new file mode 100644 index 00000000..39888a42 --- /dev/null +++ b/test/support/resources/food_item.ex @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.FoodItem do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "food_items" + repo AshPostgres.TestRepo + end + + actions do + default_accept :* + defaults [:read, :destroy, create: :*, update: :*] + end + + attributes do + uuid_primary_key :id + + attribute :name, :string do + public? true + allow_nil? false + end + + attribute :food_category_id, :uuid do + public? true + allow_nil? false + end + end + + relationships do + belongs_to :category, AshPostgres.Test.FoodCategory do + source_attribute :food_category_id + public? true + allow_nil? false + end + end + + calculations do + calculate :allowed_for_user, + :boolean, + expr( + not exists( + AshPostgres.Test.UserExcludedCategory, + user_id == ^arg(:user_id) and + food_category_id == parent(food_category_id) + ) + ) do + public? true + + argument :user_id, :uuid do + allow_nil? false + end + end + end +end diff --git a/test/support/resources/meal.ex b/test/support/resources/meal.ex new file mode 100644 index 00000000..98be429b --- /dev/null +++ b/test/support/resources/meal.ex @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.Meal do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "meals" + repo AshPostgres.TestRepo + end + + actions do + default_accept :* + defaults [:read, :destroy, create: :*, update: :*] + end + + attributes do + uuid_primary_key :id + + attribute :name, :string do + public? true + allow_nil? false + end + end + + relationships do + has_many :meal_items, AshPostgres.Test.MealItem do + public? true + end + end + + calculations do + calculate :allowed_for_user, + :boolean, + expr( + count(meal_items) == + count(meal_items, query: [filter: allowed_for_user(user_id: ^arg(:user_id))]) + ) do + public? true + + argument :user_id, :uuid do + allow_nil? false + end + end + end +end diff --git a/test/support/resources/meal_item.ex b/test/support/resources/meal_item.ex new file mode 100644 index 00000000..ff373334 --- /dev/null +++ b/test/support/resources/meal_item.ex @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.MealItem do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "meal_items" + repo AshPostgres.TestRepo + end + + actions do + default_accept :* + defaults [:read, :destroy, create: :*, update: :*] + end + + attributes do + uuid_primary_key :id + + attribute :meal_id, :uuid do + public? true + allow_nil? false + end + + attribute :food_item_id, :uuid do + public? true + allow_nil? false + end + end + + relationships do + belongs_to :meal, AshPostgres.Test.Meal do + public? true + allow_nil? false + end + + belongs_to :food_item, AshPostgres.Test.FoodItem do + public? true + allow_nil? false + end + end + + calculations do + calculate :allowed_for_user, + :boolean, + expr(food_item.allowed_for_user(user_id: ^arg(:user_id))) do + public? true + + argument :user_id, :uuid do + allow_nil? false + end + end + end +end diff --git a/test/support/resources/user_excluded_category.ex b/test/support/resources/user_excluded_category.ex new file mode 100644 index 00000000..c955277b --- /dev/null +++ b/test/support/resources/user_excluded_category.ex @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2019 ash_postgres contributors +# +# SPDX-License-Identifier: MIT + +defmodule AshPostgres.Test.UserExcludedCategory do + @moduledoc false + use Ash.Resource, + domain: AshPostgres.Test.Domain, + data_layer: AshPostgres.DataLayer + + postgres do + table "user_excluded_categories" + repo AshPostgres.TestRepo + end + + actions do + default_accept :* + defaults [:read, :destroy, create: :*, update: :*] + end + + attributes do + uuid_primary_key :id + + attribute :user_id, :uuid do + public? true + allow_nil? false + end + + attribute :food_category_id, :uuid do + public? true + allow_nil? false + end + end + + relationships do + belongs_to :food_category, AshPostgres.Test.FoodCategory do + public? true + allow_nil? false + end + end +end