|
36 | 36 | #include "catalog/pg_collation.h" |
37 | 37 | #include "catalog/pg_constraint.h" |
38 | 38 | #include "catalog/pg_depend.h" |
| 39 | +#include "catalog/pg_rewrite.h" |
39 | 40 | #include "catalog/pg_foreign_table.h" |
40 | 41 | #include "catalog/pg_inherits.h" |
41 | 42 | #include "catalog/pg_namespace.h" |
|
79 | 80 | #include "partitioning/partdesc.h" |
80 | 81 | #include "pgstat.h" |
81 | 82 | #include "rewrite/rewriteDefine.h" |
| 83 | +#include "rewrite/rewriteSupport.h" |
82 | 84 | #include "rewrite/rewriteHandler.h" |
83 | 85 | #include "rewrite/rewriteManip.h" |
84 | 86 | #include "storage/bufmgr.h" |
@@ -191,6 +193,8 @@ typedef struct AlteredTableInfo |
191 | 193 | char *clusterOnIndex; /* index to use for CLUSTER */ |
192 | 194 | List *changedStatisticsOids; /* OIDs of statistics to rebuild */ |
193 | 195 | List *changedStatisticsDefs; /* string definitions of same */ |
| 196 | + List *changedViewOids; /* OIDs of views to rebuild */ |
| 197 | + List *changedViewDefs; /* CREATE OR REPLACE VIEW strings for same */ |
194 | 198 | } AlteredTableInfo; |
195 | 199 |
|
196 | 200 | /* Struct describing one new constraint to check in Phase 3 scan */ |
@@ -526,6 +530,7 @@ static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, |
526 | 530 | static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab); |
527 | 531 | static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab); |
528 | 532 | static void RememberStatisticsForRebuilding(Oid indoid, AlteredTableInfo *tab); |
| 533 | +static void RememberViewForRebuilding(Oid viewOid, AlteredTableInfo *tab); |
529 | 534 | static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, |
530 | 535 | LOCKMODE lockmode); |
531 | 536 | static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, |
@@ -12437,18 +12442,68 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, |
12437 | 12442 | break; |
12438 | 12443 |
|
12439 | 12444 | case OCLASS_REWRITE: |
12440 | | - |
| 12445 | + { |
12441 | 12446 | /* |
12442 | | - * View/rule bodies have pretty much the same issues as |
12443 | | - * function bodies. FIXME someday. |
| 12447 | + * If the dependent object is a view's SELECT rule, we can |
| 12448 | + * rebuild the view automatically after the type change. |
| 12449 | + * For user-defined rules on regular tables we still reject |
| 12450 | + * the operation. |
12444 | 12451 | */ |
12445 | | - ereport(ERROR, |
12446 | | - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
12447 | | - errmsg("cannot alter type of a column used by a view or rule"), |
12448 | | - errdetail("%s depends on column \"%s\"", |
12449 | | - getObjectDescription(&foundObject, false), |
12450 | | - colName))); |
| 12452 | + Relation rewriteRel; |
| 12453 | + ScanKeyData rkey[1]; |
| 12454 | + SysScanDesc rscan; |
| 12455 | + HeapTuple rtup; |
| 12456 | + Oid viewOid = InvalidOid; |
| 12457 | + bool is_view_select_rule = false; |
| 12458 | + |
| 12459 | + rewriteRel = table_open(RewriteRelationId, AccessShareLock); |
| 12460 | + ScanKeyInit(&rkey[0], |
| 12461 | + Anum_pg_rewrite_oid, |
| 12462 | + BTEqualStrategyNumber, F_OIDEQ, |
| 12463 | + ObjectIdGetDatum(foundObject.objectId)); |
| 12464 | + rscan = systable_beginscan(rewriteRel, RewriteOidIndexId, |
| 12465 | + true, NULL, 1, rkey); |
| 12466 | + rtup = systable_getnext(rscan); |
| 12467 | + if (HeapTupleIsValid(rtup)) |
| 12468 | + { |
| 12469 | + Form_pg_rewrite ruleform = (Form_pg_rewrite) GETSTRUCT(rtup); |
| 12470 | + |
| 12471 | + viewOid = ruleform->ev_class; |
| 12472 | + /* |
| 12473 | + * Only rebuild if this is a view's ON SELECT rule: |
| 12474 | + * ev_type='1', is_instead=true, rulename="_RETURN", and |
| 12475 | + * the owning relation is a view. |
| 12476 | + */ |
| 12477 | + if (ruleform->ev_type == '1' && |
| 12478 | + ruleform->is_instead && |
| 12479 | + strcmp(NameStr(ruleform->rulename), |
| 12480 | + ViewSelectRuleName) == 0 && |
| 12481 | + get_rel_relkind(viewOid) == RELKIND_VIEW) |
| 12482 | + is_view_select_rule = true; |
| 12483 | + } |
| 12484 | + systable_endscan(rscan); |
| 12485 | + table_close(rewriteRel, AccessShareLock); |
| 12486 | + |
| 12487 | + if (is_view_select_rule) |
| 12488 | + { |
| 12489 | + /* |
| 12490 | + * Remember this view (and any views that depend on it) |
| 12491 | + * for rebuilding after the type change. |
| 12492 | + */ |
| 12493 | + RememberViewForRebuilding(viewOid, tab); |
| 12494 | + } |
| 12495 | + else |
| 12496 | + { |
| 12497 | + /* Non-view rules cannot be auto-rebuilt */ |
| 12498 | + ereport(ERROR, |
| 12499 | + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 12500 | + errmsg("cannot alter type of a column used by a view or rule"), |
| 12501 | + errdetail("%s depends on column \"%s\"", |
| 12502 | + getObjectDescription(&foundObject, false), |
| 12503 | + colName))); |
| 12504 | + } |
12451 | 12505 | break; |
| 12506 | + } |
12452 | 12507 |
|
12453 | 12508 | case OCLASS_TRIGGER: |
12454 | 12509 |
|
@@ -12881,6 +12936,110 @@ RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab) |
12881 | 12936 | } |
12882 | 12937 | } |
12883 | 12938 |
|
| 12939 | +/* |
| 12940 | + * Subroutine for ATExecAlterColumnType: remember that a view needs to be |
| 12941 | + * rebuilt after the column type change. Also recursively finds any views |
| 12942 | + * that depend on this view and remembers them too (in dependency order so |
| 12943 | + * that recreation will succeed). |
| 12944 | + */ |
| 12945 | +static void |
| 12946 | +RememberViewForRebuilding(Oid viewOid, AlteredTableInfo *tab) |
| 12947 | +{ |
| 12948 | + char *defstring; |
| 12949 | + Relation depRel; |
| 12950 | + ScanKeyData key[2]; |
| 12951 | + SysScanDesc scan; |
| 12952 | + HeapTuple depTup; |
| 12953 | + |
| 12954 | + /* |
| 12955 | + * De-duplication check: if already remembered, skip. This also prevents |
| 12956 | + * infinite recursion for view dependency cycles (which shouldn't exist, |
| 12957 | + * but be safe). |
| 12958 | + */ |
| 12959 | + if (list_member_oid(tab->changedViewOids, viewOid)) |
| 12960 | + return; |
| 12961 | + |
| 12962 | + /* |
| 12963 | + * Capture the view's definition string before any type changes happen. |
| 12964 | + * pg_get_view_createcommand() reads the current catalog state. |
| 12965 | + */ |
| 12966 | + defstring = pg_get_view_createcommand(viewOid); |
| 12967 | + |
| 12968 | + tab->changedViewOids = lappend_oid(tab->changedViewOids, viewOid); |
| 12969 | + tab->changedViewDefs = lappend(tab->changedViewDefs, defstring); |
| 12970 | + |
| 12971 | + /* |
| 12972 | + * Now find any views that depend on this view and remember them too. |
| 12973 | + * We need to do this recursively so that when we recreate views, we |
| 12974 | + * can do so in topological order (base views first). |
| 12975 | + */ |
| 12976 | + depRel = table_open(DependRelationId, AccessShareLock); |
| 12977 | + |
| 12978 | + ScanKeyInit(&key[0], |
| 12979 | + Anum_pg_depend_refclassid, |
| 12980 | + BTEqualStrategyNumber, F_OIDEQ, |
| 12981 | + ObjectIdGetDatum(RelationRelationId)); |
| 12982 | + ScanKeyInit(&key[1], |
| 12983 | + Anum_pg_depend_refobjid, |
| 12984 | + BTEqualStrategyNumber, F_OIDEQ, |
| 12985 | + ObjectIdGetDatum(viewOid)); |
| 12986 | + |
| 12987 | + scan = systable_beginscan(depRel, DependReferenceIndexId, true, |
| 12988 | + NULL, 2, key); |
| 12989 | + |
| 12990 | + while (HeapTupleIsValid(depTup = systable_getnext(scan))) |
| 12991 | + { |
| 12992 | + Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); |
| 12993 | + |
| 12994 | + /* |
| 12995 | + * We're looking for rewrite rules that depend on this view as a |
| 12996 | + * whole (not on a specific column). These correspond to views that |
| 12997 | + * SELECT from this view. |
| 12998 | + */ |
| 12999 | + if (foundDep->classid == RewriteRelationId && |
| 13000 | + foundDep->deptype != DEPENDENCY_PIN) |
| 13001 | + { |
| 13002 | + Relation rewriteRel; |
| 13003 | + ScanKeyData rkey[1]; |
| 13004 | + SysScanDesc rscan; |
| 13005 | + HeapTuple rtup; |
| 13006 | + |
| 13007 | + rewriteRel = table_open(RewriteRelationId, AccessShareLock); |
| 13008 | + ScanKeyInit(&rkey[0], |
| 13009 | + Anum_pg_rewrite_oid, |
| 13010 | + BTEqualStrategyNumber, F_OIDEQ, |
| 13011 | + ObjectIdGetDatum(foundDep->objid)); |
| 13012 | + rscan = systable_beginscan(rewriteRel, RewriteOidIndexId, |
| 13013 | + true, NULL, 1, rkey); |
| 13014 | + rtup = systable_getnext(rscan); |
| 13015 | + if (HeapTupleIsValid(rtup)) |
| 13016 | + { |
| 13017 | + Form_pg_rewrite ruleform = (Form_pg_rewrite) GETSTRUCT(rtup); |
| 13018 | + Oid depViewOid = ruleform->ev_class; |
| 13019 | + |
| 13020 | + /* |
| 13021 | + * Only process SELECT rules of views (not other rule types |
| 13022 | + * or rules on regular tables). |
| 13023 | + */ |
| 13024 | + if (ruleform->ev_type == '1' && |
| 13025 | + ruleform->is_instead && |
| 13026 | + strcmp(NameStr(ruleform->rulename), |
| 13027 | + ViewSelectRuleName) == 0 && |
| 13028 | + get_rel_relkind(depViewOid) == RELKIND_VIEW) |
| 13029 | + { |
| 13030 | + /* Recursively remember this dependent view */ |
| 13031 | + RememberViewForRebuilding(depViewOid, tab); |
| 13032 | + } |
| 13033 | + } |
| 13034 | + systable_endscan(rscan); |
| 13035 | + table_close(rewriteRel, AccessShareLock); |
| 13036 | + } |
| 13037 | + } |
| 13038 | + |
| 13039 | + systable_endscan(scan); |
| 13040 | + table_close(depRel, AccessShareLock); |
| 13041 | +} |
| 13042 | + |
12884 | 13043 | /* |
12885 | 13044 | * Cleanup after we've finished all the ALTER TYPE operations for a |
12886 | 13045 | * particular relation. We have to drop and recreate all the indexes |
@@ -13057,17 +13216,62 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) |
13057 | 13216 | lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd); |
13058 | 13217 | } |
13059 | 13218 |
|
| 13219 | + /* |
| 13220 | + * Add views to the drop list. We must drop them here and recreate them |
| 13221 | + * later (via afterStmts), because CREATE OR REPLACE VIEW refuses to |
| 13222 | + * change a view's output column types. All dependent views (including |
| 13223 | + * transitively dependent ones) were already collected in changedViewOids |
| 13224 | + * by RememberViewForRebuilding, so DROP_RESTRICT is still safe. |
| 13225 | + */ |
| 13226 | + foreach(oid_item, tab->changedViewOids) |
| 13227 | + { |
| 13228 | + Oid viewOid = lfirst_oid(oid_item); |
| 13229 | + |
| 13230 | + ObjectAddressSet(obj, RelationRelationId, viewOid); |
| 13231 | + add_exact_object_address(&obj, objects); |
| 13232 | + } |
| 13233 | + |
13060 | 13234 | /* |
13061 | 13235 | * It should be okay to use DROP_RESTRICT here, since nothing else should |
13062 | | - * be depending on these objects. |
| 13236 | + * be depending on these objects. All transitive view dependencies have |
| 13237 | + * been collected in changedViewOids, so DROP_RESTRICT will not fail due |
| 13238 | + * to unrecorded dependents. |
13063 | 13239 | */ |
13064 | 13240 | performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); |
13065 | 13241 |
|
13066 | 13242 | free_object_addresses(objects); |
13067 | 13243 |
|
13068 | 13244 | /* |
13069 | | - * The objects will get recreated during subsequent passes over the work |
13070 | | - * queue. |
| 13245 | + * Queue up CREATE OR REPLACE VIEW statements to recreate the dropped |
| 13246 | + * views. They will be executed (in order) at the end of Phase 3 via |
| 13247 | + * tab->afterStmts, after all the work-queue entries have run. |
| 13248 | + * |
| 13249 | + * The views are in changedViewOids/changedViewDefs in dependency order |
| 13250 | + * (base views first), so they will be recreated in the correct order. |
| 13251 | + */ |
| 13252 | + foreach(def_item, tab->changedViewDefs) |
| 13253 | + { |
| 13254 | + char *viewcmd = (char *) lfirst(def_item); |
| 13255 | + List *raw_parsetree_list; |
| 13256 | + ListCell *lc; |
| 13257 | + |
| 13258 | + /* |
| 13259 | + * Parse the saved CREATE OR REPLACE VIEW command. If the new column |
| 13260 | + * type is incompatible with the view's query (e.g., an operator is |
| 13261 | + * not available), this will fail and roll back the whole ALTER TABLE. |
| 13262 | + */ |
| 13263 | + raw_parsetree_list = raw_parser(viewcmd, RAW_PARSE_DEFAULT); |
| 13264 | + |
| 13265 | + foreach(lc, raw_parsetree_list) |
| 13266 | + { |
| 13267 | + tab->afterStmts = lappend(tab->afterStmts, |
| 13268 | + ((RawStmt *) lfirst(lc))->stmt); |
| 13269 | + } |
| 13270 | + } |
| 13271 | + |
| 13272 | + /* |
| 13273 | + * The constraint/index/statistics objects will get recreated during |
| 13274 | + * subsequent passes over the work queue. |
13071 | 13275 | */ |
13072 | 13276 | } |
13073 | 13277 |
|
|
0 commit comments