From ef58c783137a55a675f152b2fa082bff0e3023e4 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 12 Jul 2024 23:23:42 -0400 Subject: [PATCH] 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 db7716d..609bb48 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 002303e..002822c 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 fc465fb..1d939f8 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 e084bbf..40d20b7 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 0000000..60d6a6d --- /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 0000000..97101dc --- /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 0000000..af406cf --- /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 573b7f2..9bfc8b8 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 e9f46e5..980470a 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)