From 1d0437a675b883ae10298d72b758a8b5b67356cc Mon Sep 17 00:00:00 2001
From: Zach Daniel <zach@zachdaniel.dev>
Date: Mon, 13 Jan 2025 16:12:19 -0500
Subject: [PATCH] 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