Packages

phoenix_kit

1.7.54
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit migrations postgres v72.ex
Raw

lib/phoenix_kit/migrations/postgres/v72.ex

defmodule PhoenixKit.Migrations.Postgres.V72 do
@moduledoc """
V72: Rename `id``uuid` on Category A tables + add missing FK constraints.
Category A tables have a UUID primary key column named `id`. Ecto schemas map
field `:uuid` → DB column `id` via `source: :id`. This migration renames the
DB column to `uuid` so the `source: :id` mapping is no longer needed.
## Changes
- Rename `id``uuid` on 30 Category A tables (metadata-only, instant)
- Add 4 missing FK constraints:
- `phoenix_kit_comments.user_uuid``phoenix_kit_users.uuid` (CASCADE)
- `phoenix_kit_comments_dislikes.user_uuid``phoenix_kit_users.uuid` (CASCADE)
- `phoenix_kit_comments_likes.user_uuid``phoenix_kit_users.uuid` (CASCADE)
- `phoenix_kit_scheduled_jobs.created_by_uuid``phoenix_kit_users.uuid` (SET NULL)
All operations are idempotent (guarded by column/constraint existence checks).
## Safety
- Column renames are metadata-only in PostgreSQL — zero downtime, instant
- 29 existing FK constraints referencing these columns auto-update on rename
- Deploy is atomic: migration runs on startup before new code serves traffic
"""
use Ecto.Migration
@category_a_tables ~w(
phoenix_kit_buckets phoenix_kit_comment_dislikes phoenix_kit_comment_likes
phoenix_kit_comments phoenix_kit_comments_dislikes phoenix_kit_comments_likes
phoenix_kit_file_instances phoenix_kit_file_locations phoenix_kit_files
phoenix_kit_post_comments phoenix_kit_post_dislikes phoenix_kit_post_groups
phoenix_kit_post_likes phoenix_kit_post_media phoenix_kit_post_mentions
phoenix_kit_post_tags phoenix_kit_post_views phoenix_kit_posts
phoenix_kit_scheduled_jobs phoenix_kit_storage_dimensions
phoenix_kit_ticket_attachments phoenix_kit_ticket_comments
phoenix_kit_ticket_status_history phoenix_kit_tickets
phoenix_kit_user_blocks phoenix_kit_user_blocks_history
phoenix_kit_user_connections phoenix_kit_user_connections_history
phoenix_kit_user_follows phoenix_kit_user_follows_history
)
# {table, fk_column, ref_table, ref_column, on_delete}
@missing_fk_constraints [
{"phoenix_kit_comments", "user_uuid", "phoenix_kit_users", "uuid", "CASCADE"},
{"phoenix_kit_comments_dislikes", "user_uuid", "phoenix_kit_users", "uuid", "CASCADE"},
{"phoenix_kit_comments_likes", "user_uuid", "phoenix_kit_users", "uuid", "CASCADE"},
{"phoenix_kit_scheduled_jobs", "created_by_uuid", "phoenix_kit_users", "uuid", "SET NULL"}
]
def up(%{prefix: prefix} = opts) do
escaped_prefix = Map.get(opts, :escaped_prefix, prefix)
flush()
# 1. Rename id → uuid on all Category A tables
for table <- @category_a_tables do
if table_exists?(table, escaped_prefix) and
column_exists?(table, "id", escaped_prefix) and
not column_exists?(table, "uuid", escaped_prefix) do
execute("ALTER TABLE #{prefix_table(table, prefix)} RENAME COLUMN id TO uuid")
end
end
# 2. Add missing FK constraints
for {table, fk_col, ref_table, ref_col, on_delete} <- @missing_fk_constraints do
add_fk_constraint(table, fk_col, ref_table, ref_col, on_delete, prefix, escaped_prefix)
end
execute("COMMENT ON TABLE #{prefix_table("phoenix_kit", prefix)} IS '72'")
end
def down(%{prefix: prefix} = opts) do
escaped_prefix = Map.get(opts, :escaped_prefix, prefix)
# 1. Drop the 4 FK constraints
for {table, fk_col, _ref_table, _ref_col, _on_delete} <- @missing_fk_constraints do
drop_fk_constraint(table, fk_col, prefix, escaped_prefix)
end
# 2. Rename uuid → id on all Category A tables
for table <- @category_a_tables do
if table_exists?(table, escaped_prefix) and
column_exists?(table, "uuid", escaped_prefix) and
not column_exists?(table, "id", escaped_prefix) do
execute("ALTER TABLE #{prefix_table(table, prefix)} RENAME COLUMN uuid TO id")
end
end
execute("COMMENT ON TABLE #{prefix_table("phoenix_kit", prefix)} IS '71'")
end
# ---------------------------------------------------------------------------
# FK Constraint Helpers
# ---------------------------------------------------------------------------
defp add_fk_constraint(table, fk_col, ref_table, ref_col, on_delete, prefix, escaped_prefix) do
if table_exists?(table, escaped_prefix) and
column_exists?(table, fk_col, escaped_prefix) and
table_exists?(ref_table, escaped_prefix) and
column_exists?(ref_table, ref_col, escaped_prefix) do
table_name = prefix_table(table, prefix)
ref_name = prefix_table(ref_table, prefix)
constraint = fk_constraint_name(table, fk_col)
# Clean up orphaned FK references before adding the constraint
cleanup_orphaned_fk_refs(table_name, fk_col, ref_name, ref_col, on_delete)
execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = '#{constraint}'
AND conrelid = '#{table_name}'::regclass
) THEN
ALTER TABLE #{table_name}
ADD CONSTRAINT #{constraint}
FOREIGN KEY (#{fk_col})
REFERENCES #{ref_name}(#{ref_col})
ON DELETE #{on_delete};
END IF;
END $$;
""")
end
end
defp cleanup_orphaned_fk_refs(table_name, fk_col, ref_name, ref_col, on_delete) do
{action, action_sql} =
if on_delete == "CASCADE" do
{"DELETE",
"""
DELETE FROM #{table_name} t
WHERE t.#{fk_col} IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM #{ref_name} r WHERE r.#{ref_col} = t.#{fk_col}
)
"""}
else
{"SET NULL",
"""
UPDATE #{table_name} t
SET #{fk_col} = NULL
WHERE t.#{fk_col} IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM #{ref_name} r WHERE r.#{ref_col} = t.#{fk_col}
)
"""}
end
execute("""
DO $$
DECLARE
affected INTEGER;
BEGIN
#{action_sql};
GET DIAGNOSTICS affected = ROW_COUNT;
IF affected > 0 THEN
RAISE NOTICE 'PhoenixKit V72: cleaned up % orphaned rows in %.% (action: %)',
affected, '#{table_name}', '#{fk_col}', '#{action}';
END IF;
END $$;
""")
end
defp drop_fk_constraint(table, fk_col, prefix, escaped_prefix) do
if table_exists?(table, escaped_prefix) do
table_name = prefix_table(table, prefix)
constraint = fk_constraint_name(table, fk_col)
execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = '#{constraint}'
AND conrelid = '#{table_name}'::regclass
) THEN
ALTER TABLE #{table_name}
DROP CONSTRAINT #{constraint};
END IF;
END $$;
""")
end
end
defp fk_constraint_name(table, fk_col) do
short = String.replace_prefix(table, "phoenix_kit_", "")
"fk_#{short}_#{fk_col}"
end
# ---------------------------------------------------------------------------
# Introspection Helpers
# ---------------------------------------------------------------------------
defp table_exists?(table, escaped_prefix) do
case repo().query(
"""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = '#{table}'
AND table_schema = '#{escaped_prefix}'
)
""",
[],
log: false
) do
{:ok, %{rows: [[true]]}} -> true
_ -> false
end
end
defp column_exists?(table, column, escaped_prefix) do
case repo().query(
"""
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = '#{table}'
AND column_name = '#{column}'
AND table_schema = '#{escaped_prefix}'
)
""",
[],
log: false
) do
{:ok, %{rows: [[true]]}} -> true
_ -> false
end
end
defp prefix_table(table_name, nil), do: table_name
defp prefix_table(table_name, "public"), do: "public.#{table_name}"
defp prefix_table(table_name, prefix), do: "#{prefix}.#{table_name}"
end