11defmodule Console.Schema.PolicyBinding do
22 use Piazza.Ecto.Schema
33 alias Console.Schema . { User , Group }
4+ alias Console.Repo
45
56 schema "policy_bindings" do
67 field :policy_id , :binary_id
@@ -10,6 +11,66 @@ defmodule Console.Schema.PolicyBinding do
1011 timestamps ( )
1112 end
1213
14+ @ doc """
15+ Fetches all tables and their policy-related columns from the database schema.
16+ Returns a map of table_name => [column_names] for uuid columns ending in '_policy_id' or 'bindings_id'.
17+ """
18+ def policy_columns do
19+ query = """
20+ SELECT table_name, column_name
21+ FROM information_schema.columns
22+ WHERE (column_name LIKE '%policy_id' OR column_name = 'bindings_id')
23+ AND table_schema = 'public'
24+ AND table_name != 'policy_bindings'
25+ AND data_type = 'uuid'
26+ ORDER BY table_name, column_name
27+ """
28+
29+ Repo . query! ( query , [ ] , timeout: 30_000 ) . rows
30+ |> Enum . group_by ( fn [ table , _col ] -> table end , fn [ _table , col ] -> col end )
31+ end
32+
33+ @ doc """
34+ Returns IDs of dangling policy bindings (bindings whose policy_id is not referenced anywhere).
35+ Uses database introspection to dynamically find all tables with policy references.
36+ """
37+ def dangling_ids do
38+ table_columns = policy_columns ( )
39+ where_clause = build_where_clause ( table_columns )
40+
41+ sql = """
42+ SELECT pb.id FROM policy_bindings pb
43+ WHERE #{ where_clause }
44+ ORDER BY pb.id ASC
45+ """
46+
47+ Repo . query! ( sql , [ ] , timeout: 300_000 ) . rows
48+ |> List . flatten ( )
49+ |> Enum . map ( & Ecto.UUID . load! / 1 )
50+ end
51+
52+ @ doc """
53+ Returns an Ecto query for dangling policy bindings.
54+ Note: This loads dangling IDs first, so for very large datasets consider using dangling_ids/0 directly.
55+ """
56+ def dangling ( query \\ __MODULE__ ) do
57+ ids = dangling_ids ( )
58+ from ( p in query , where: p . id in ^ ids )
59+ end
60+
61+ defp build_where_clause ( table_columns ) do
62+ table_columns
63+ |> Enum . map ( fn { table , columns } ->
64+ conditions = Enum . map_join ( columns , " OR " , fn col -> "#{ col } = pb.policy_id" end )
65+ "NOT EXISTS(SELECT 1 FROM #{ table } WHERE #{ conditions } )"
66+ end )
67+ |> Enum . join ( " AND " )
68+ end
69+
70+ def ordered ( query \\ __MODULE__ , order \\ [ asc: :id ] ) do
71+ from ( p in query , order_by: ^ order )
72+ end
73+
1374 @ valid ~w( user_id group_id policy_id) a
1475
1576 def changeset ( model , attrs \\ % { } ) do
0 commit comments