Skip to content

Commit 03a588d

Browse files
authored
Merge pull request IvorySQL#1224 from bigplaice/allow_col_type_change_for_view
allow column type change even view depends on the column
2 parents 206f547 + 1f5ce86 commit 03a588d

6 files changed

Lines changed: 1135 additions & 12 deletions

File tree

src/backend/commands/tablecmds.c

Lines changed: 216 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "catalog/pg_collation.h"
3737
#include "catalog/pg_constraint.h"
3838
#include "catalog/pg_depend.h"
39+
#include "catalog/pg_rewrite.h"
3940
#include "catalog/pg_foreign_table.h"
4041
#include "catalog/pg_inherits.h"
4142
#include "catalog/pg_namespace.h"
@@ -79,6 +80,7 @@
7980
#include "partitioning/partdesc.h"
8081
#include "pgstat.h"
8182
#include "rewrite/rewriteDefine.h"
83+
#include "rewrite/rewriteSupport.h"
8284
#include "rewrite/rewriteHandler.h"
8385
#include "rewrite/rewriteManip.h"
8486
#include "storage/bufmgr.h"
@@ -191,6 +193,8 @@ typedef struct AlteredTableInfo
191193
char *clusterOnIndex; /* index to use for CLUSTER */
192194
List *changedStatisticsOids; /* OIDs of statistics to rebuild */
193195
List *changedStatisticsDefs; /* string definitions of same */
196+
List *changedViewOids; /* OIDs of views to rebuild */
197+
List *changedViewDefs; /* CREATE OR REPLACE VIEW strings for same */
194198
} AlteredTableInfo;
195199

196200
/* Struct describing one new constraint to check in Phase 3 scan */
@@ -526,6 +530,7 @@ static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
526530
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
527531
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
528532
static void RememberStatisticsForRebuilding(Oid indoid, AlteredTableInfo *tab);
533+
static void RememberViewForRebuilding(Oid viewOid, AlteredTableInfo *tab);
529534
static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
530535
LOCKMODE lockmode);
531536
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
@@ -12437,18 +12442,68 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
1243712442
break;
1243812443

1243912444
case OCLASS_REWRITE:
12440-
12445+
{
1244112446
/*
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.
1244412451
*/
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+
}
1245112505
break;
12506+
}
1245212507

1245312508
case OCLASS_TRIGGER:
1245412509

@@ -12881,6 +12936,110 @@ RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
1288112936
}
1288212937
}
1288312938

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+
1288413043
/*
1288513044
* Cleanup after we've finished all the ALTER TYPE operations for a
1288613045
* particular relation. We have to drop and recreate all the indexes
@@ -13057,17 +13216,62 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
1305713216
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
1305813217
}
1305913218

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+
1306013234
/*
1306113235
* 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.
1306313239
*/
1306413240
performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
1306513241

1306613242
free_object_addresses(objects);
1306713243

1306813244
/*
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.
1307113275
*/
1307213276
}
1307313277

0 commit comments

Comments
 (0)