diff --git a/lib/custom_extension.ex b/lib/custom_extension.ex new file mode 100644 index 0000000..9360dc5 --- /dev/null +++ b/lib/custom_extension.ex @@ -0,0 +1,20 @@ +defmodule AshPostgres.CustomExtension do + @moduledoc """ + A custom extension implementation. + """ + + @callback install(version :: integer) :: String.t() + + @callback uninstall(version :: integer) :: String.t() + + defmacro __using__(name: name, latest_version: latest_version) do + quote do + @behaviour AshPostgres.CustomExtension + + @extension_name unquote(name) + @extension_latest_version unquote(latest_version) + + def extension, do: {@extension_name, @extension_latest_version, &install/1, &uninstall/1} + end + end +end diff --git a/lib/expr.ex b/lib/expr.ex index e2b5569..188de1e 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -1106,8 +1106,7 @@ defmodule AshPostgres.Expr 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.primary_key(first_relationship.destination) pkey_attr = Ash.Resource.Info.attribute(first_relationship.destination, pkey_attr) diff --git a/lib/join.ex b/lib/join.ex index b67c74d..83c5f4c 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -264,16 +264,15 @@ defmodule AshPostgres.Join do ) end - parent_bindings = - %{ - base_bindings - | resource: relationship.source, - calculations: %{}, - parent_resources: [], - aggregate_defs: %{}, - context: relationship.context, - current: parent_binding + 1 - } + parent_bindings = %{ + base_bindings + | resource: relationship.source, + calculations: %{}, + parent_resources: [], + aggregate_defs: %{}, + context: relationship.context, + current: parent_binding + 1 + } parent_bindings = if bindings do diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index f80dc88..67386ca 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -180,10 +180,25 @@ defmodule AshPostgres.MigrationGenerator do {other, other.installed} end - to_install = List.wrap(repo.installed_extensions()) -- List.wrap(installed_extensions) + requesteds = + repo.installed_extensions() + |> Enum.map(fn + extension_module when is_atom(extension_module) -> + {ext_name, version, _up_fn, _down_fn} = extension = extension_module.extension() + + {"#{ext_name}_v#{version}", extension} + + extension_name -> + {extension_name, extension_name} + end) to_install = - if "ash-functions" in repo.installed_extensions() && + requesteds + |> Enum.filter(fn {name, _extension} -> !Enum.member?(installed_extensions, name) end) + |> Enum.map(fn {_name, extension} -> extension end) + + to_install = + if "ash-functions" in requesteds && extensions_snapshot[:ash_functions_version] != @latest_ash_functions_version do Enum.uniq(["ash-functions" | to_install]) @@ -197,6 +212,10 @@ defmodule AshPostgres.MigrationGenerator do else {module, migration_name} = case to_install do + [{ext_name, version, _up_fn, _down_fn}] -> + {"install_#{ext_name}_v#{version}", + "#{timestamp(true)}_install_#{ext_name}_v#{version}_extension"} + [single] -> {"install_#{single}", "#{timestamp(true)}_install_#{single}_extension"} @@ -222,6 +241,9 @@ defmodule AshPostgres.MigrationGenerator do "ash-functions" -> install_ash_functions(extensions_snapshot[:ash_functions_version]) + {_ext_name, version, up_fn, _down_fn} when is_function(up_fn, 1) -> + up_fn.(version) + extension -> "execute(\"CREATE EXTENSION IF NOT EXISTS \\\"#{extension}\\\"\")" end) @@ -231,6 +253,9 @@ defmodule AshPostgres.MigrationGenerator do "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)\")" + {_ext_name, version, _up_fn, down_fn} when is_function(down_fn, 1) -> + down_fn.(version) + extension -> "# execute(\"DROP EXTENSION IF EXISTS \\\"#{extension}\\\"\")" end) @@ -257,12 +282,14 @@ defmodule AshPostgres.MigrationGenerator do end """ + installed = Enum.map(requesteds, fn {name, _extension} -> name end) + snapshot_contents = Jason.encode!( %{ - installed: repo.installed_extensions() + installed: installed } - |> set_ash_functions(repo.installed_extensions()), + |> set_ash_functions(installed), pretty: true ) diff --git a/priv/resource_snapshots/extensions.json b/priv/resource_snapshots/extensions.json index bf67de7..08e5809 100644 --- a/priv/resource_snapshots/extensions.json +++ b/priv/resource_snapshots/extensions.json @@ -4,6 +4,7 @@ "ash-functions", "uuid-ossp", "pg_trgm", - "citext" + "citext", + "demo-functions_v1" ] } \ No newline at end of file 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 new file mode 100644 index 0000000..5521466 --- /dev/null +++ b/priv/test_repo/migrations/20230804223759_install_demo-functions_v0_extension.exs @@ -0,0 +1,26 @@ +defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV0 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_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_demo_functions() + """) + end +end \ No newline at end of file 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 new file mode 100644 index 0000000..b82ee5c --- /dev/null +++ b/priv/test_repo/migrations/20230804223818_install_demo-functions_v1_extension.exs @@ -0,0 +1,26 @@ +defmodule AshPostgres.TestRepo.Migrations.InstallDemoFunctionsV1 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_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_demo_functions() + """) + end +end \ No newline at end of file diff --git a/test/support/test_custom_extension.ex b/test/support/test_custom_extension.ex new file mode 100644 index 0000000..fba6680 --- /dev/null +++ b/test/support/test_custom_extension.ex @@ -0,0 +1,38 @@ +defmodule AshPostgres.TestCustomExtension do + @moduledoc false + + use AshPostgres.CustomExtension, name: "demo-functions", latest_version: 1 + + @impl true + def install(0) do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_demo_functions() + RETURNS boolean AS $$ SELECT TRUE $$ + LANGUAGE SQL + IMMUTABLE; + \"\"\") + """ + end + + @impl true + def install(1) do + """ + execute(\"\"\" + CREATE OR REPLACE FUNCTION ash_demo_functions() + RETURNS boolean AS $$ SELECT FALSE $$ + LANGUAGE SQL + IMMUTABLE; + \"\"\") + """ + end + + @impl true + def uninstall(_version) do + """ + execute(\"\"\" + DROP FUNCTION IF EXISTS ash_demo_functions() + \"\"\") + """ + end +end diff --git a/test/support/test_no_sandbox_repo.ex b/test/support/test_no_sandbox_repo.ex index c98ce9e..1fd79f6 100644 --- a/test/support/test_no_sandbox_repo.ex +++ b/test/support/test_no_sandbox_repo.ex @@ -8,7 +8,7 @@ defmodule AshPostgres.TestNoSandboxRepo do end def installed_extensions do - ["ash-functions", "uuid-ossp", "pg_trgm", "citext"] -- + ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) end diff --git a/test/support/test_repo.ex b/test/support/test_repo.ex index db90b0c..84781c6 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"] -- + ["ash-functions", "uuid-ossp", "pg_trgm", "citext", AshPostgres.TestCustomExtension] -- Application.get_env(:ash_postgres, :no_extensions, []) end diff --git a/test_snapshot_path/extensions.json b/test_snapshot_path/extensions.json index c580c1d..08e5809 100644 --- a/test_snapshot_path/extensions.json +++ b/test_snapshot_path/extensions.json @@ -1,9 +1,10 @@ { + "ash_functions_version": 1, "installed": [ "ash-functions", "uuid-ossp", "pg_trgm", - "citext" - ], - "ash_functions_version": 1 + "citext", + "demo-functions_v1" + ] } \ No newline at end of file