@@ -40,17 +40,42 @@ final class PostgresColumnDependants {
4040 * @return array{views: array<int, array<string, mixed>>, policies: array<int, array<string, string>>, triggers: array<int, array<string, string>>, generated: array<int, array<string, mixed>>, defaultGrantees: string[]}
4141 */
4242 public static function find (Connection $ connection , string $ table , string $ column , bool $ regenerate = false ): array {
43- $ views = self ::views ($ connection , $ table , $ column );
43+ $ seed = self ::targetColumnCte ($ connection );
44+ $ views = self ::views ($ connection , $ seed , 'tc.rel_oid = d.refobjid AND d.refobjsubid = tc.attnum ' , [$ table , $ column ]);
4445 $ viewOids = array_map (fn (array $ v ) => (int ) $ v ['oid ' ], $ views );
4546
46- $ dependants = [
47+ return self :: withDefaultGrantees ( $ connection , [
4748 'views ' => array_map ([self ::class, 'withoutOid ' ], $ views ),
48- 'policies ' => self ::policies ($ connection , $ table , $ column , $ viewOids ),
49+ 'policies ' => self ::policies ($ connection , $ seed , [ $ table , $ column] , $ viewOids ),
4950 'triggers ' => self ::triggers ($ connection , $ table , $ column ),
5051 'generated ' => self ::generatedColumns ($ connection , $ table , $ column , $ regenerate ),
5152 'defaultGrantees ' => [],
52- ];
53+ ]);
54+ }
55+
56+ /**
57+ * What stands in the way of dropping a view or materialised view: the
58+ * views built on it, at any depth, and the policies reading it or them.
59+ * Each comes with everything needed to put it back, as find() gives it.
60+ *
61+ * @return array{views: array<int, array<string, mixed>>, policies: array<int, array<string, string>>, triggers: array{}, generated: array{}, defaultGrantees: string[]}
62+ */
63+ public static function ofRelation (Connection $ connection , string $ schema , string $ name ): array {
64+ $ seed = 'target_col AS (SELECT to_regclass(?)::oid AS rel_oid, NULL::int2 AS attnum) ' ;
65+ $ bindings = [PostgresSchemaHelper::qualifiedName ($ schema , $ name )];
66+ $ views = self ::views ($ connection , $ seed , 'tc.rel_oid = d.refobjid ' , $ bindings );
67+ $ viewOids = array_map (fn (array $ v ) => (int ) $ v ['oid ' ], $ views );
5368
69+ return self ::withDefaultGrantees ($ connection , [
70+ 'views ' => array_map ([self ::class, 'withoutOid ' ], $ views ),
71+ 'policies ' => self ::policies ($ connection , $ seed , $ bindings , $ viewOids , true ),
72+ 'triggers ' => [],
73+ 'generated ' => [],
74+ 'defaultGrantees ' => [],
75+ ]);
76+ }
77+
78+ private static function withDefaultGrantees (Connection $ connection , array $ dependants ): array {
5479 if ($ dependants ['views ' ] !== []) {
5580 $ dependants ['defaultGrantees ' ] = array_column ($ connection ->select (
5681 "SELECT DISTINCT CASE WHEN a.grantee = 0 THEN 'PUBLIC'
@@ -137,15 +162,19 @@ private static function targetColumnCte(Connection $connection): string {
137162 * Views and materialised views reading the column, directly or through
138163 * another view, with everything needed to put each back as it was.
139164 */
140- private static function views (Connection $ connection , string $ table , string $ column ): array {
165+ /**
166+ * @param string $seed the `target_col (rel_oid, attnum)` CTE
167+ * @param string $match how a view's rewrite rule reading it is recognised
168+ */
169+ private static function views (Connection $ connection , string $ seed , string $ match , array $ bindings ): array {
141170 $ rows = $ connection ->select (
142- "WITH RECURSIVE " . self :: targetColumnCte ( $ connection ) . ",
171+ "WITH RECURSIVE " . $ seed . ",
143172 deps AS (
144173 SELECT DISTINCT dependent.oid AS view_oid, 1 AS depth
145174 FROM pg_depend d
146175 JOIN pg_rewrite r ON r.oid = d.objid
147176 JOIN pg_class dependent ON dependent.oid = r.ev_class
148- JOIN target_col tc ON tc.rel_oid = d.refobjid AND d.refobjsubid = tc.attnum
177+ JOIN target_col tc ON $ match
149178 WHERE d.classid = 'pg_rewrite'::regclass
150179 AND d.refclassid = 'pg_class'::regclass
151180 AND dependent.oid <> tc.rel_oid
@@ -197,7 +226,7 @@ private static function views(Connection $connection, string $table, string $col
197226 JOIN pg_class c ON c.oid = deepest.view_oid
198227 JOIN pg_namespace n ON n.oid = c.relnamespace
199228 ORDER BY deepest.depth, n.nspname, c.relname " ,
200- [ $ table , $ column ]
229+ $ bindings
201230 );
202231
203232 $ views = [];
@@ -230,11 +259,12 @@ private static function views(Connection $connection, string $table, string $col
230259 *
231260 * @param int[] $viewOids
232261 */
233- private static function policies (Connection $ connection , string $ table , string $ column , array $ viewOids ): array {
262+ private static function policies (Connection $ connection , string $ seed , array $ bindings , array $ viewOids, bool $ wholeRelation = false ): array {
234263 $ viewList = $ viewOids === [] ? '0 ' : implode (', ' , array_map ('intval ' , $ viewOids ));
264+ $ reads = $ wholeRelation ? 'tc.rel_oid = d.refobjid ' : 'tc.rel_oid = d.refobjid AND tc.attnum = d.refobjsubid ' ;
235265
236266 $ rows = $ connection ->select (
237- "WITH " . self :: targetColumnCte ( $ connection ) . "
267+ "WITH " . $ seed . "
238268 SELECT DISTINCT n.nspname AS schema, c.relname AS table_name, p.polname AS name,
239269 p.polpermissive AS permissive,
240270 CASE p.polcmd WHEN 'r' THEN 'SELECT' WHEN 'a' THEN 'INSERT'
@@ -249,12 +279,12 @@ private static function policies(Connection $connection, string $table, string $
249279 JOIN pg_policy p ON p.oid = d.objid
250280 JOIN pg_class c ON c.oid = p.polrelid
251281 JOIN pg_namespace n ON n.oid = c.relnamespace
252- LEFT JOIN target_col tc ON tc.rel_oid = d.refobjid AND tc.attnum = d.refobjsubid
282+ LEFT JOIN target_col tc ON $ reads
253283 WHERE d.classid = 'pg_policy'::regclass
254284 AND d.refclassid = 'pg_class'::regclass
255285 AND (tc.rel_oid IS NOT NULL OR d.refobjid IN ( $ viewList))
256286 ORDER BY n.nspname, c.relname, p.polname " ,
257- [ $ table , $ column ]
287+ $ bindings
258288 );
259289
260290 $ policies = [];
0 commit comments