81
81
#include "utils/snapmgr.h"
82
82
#include "utils/syscache.h"
83
83
#include "utils/tqual.h"
84
+ #include "utils/typcache.h"
84
85
85
86
86
87
/*
@@ -357,6 +358,9 @@ static void ATExecEnableDisableRule(Relation rel, char *rulename,
357
358
static void ATPrepAddInherit (Relation child_rel );
358
359
static void ATExecAddInherit (Relation child_rel , RangeVar * parent , LOCKMODE lockmode );
359
360
static void ATExecDropInherit (Relation rel , RangeVar * parent , LOCKMODE lockmode );
361
+ static void drop_parent_dependency (Oid relid , Oid refclassid , Oid refobjid );
362
+ static void ATExecAddOf (Relation rel , const TypeName * ofTypename , LOCKMODE lockmode );
363
+ static void ATExecDropOf (Relation rel , LOCKMODE lockmode );
360
364
static void ATExecGenericOptions (Relation rel , List * options );
361
365
362
366
static void copy_relation_data (SMgrRelation rel , SMgrRelation dst ,
@@ -2683,6 +2687,16 @@ AlterTableGetLockLevel(List *cmds)
2683
2687
cmd_lockmode = ShareUpdateExclusiveLock ;
2684
2688
break ;
2685
2689
2690
+ /*
2691
+ * These subcommands affect implicit row type conversion. They
2692
+ * have affects similar to CREATE/DROP CAST on queries. We
2693
+ * don't provide for invalidating parse trees as a result of
2694
+ * such changes. Do avoid concurrent pg_class updates, though.
2695
+ */
2696
+ case AT_AddOf :
2697
+ case AT_DropOf :
2698
+ cmd_lockmode = ShareUpdateExclusiveLock ;
2699
+
2686
2700
/*
2687
2701
* These subcommands affect general strategies for performance
2688
2702
* and maintenance, though don't change the semantic results
@@ -2942,13 +2956,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
2942
2956
case AT_EnableAlwaysRule :
2943
2957
case AT_EnableReplicaRule :
2944
2958
case AT_DisableRule :
2945
- ATSimplePermissions (rel , ATT_TABLE );
2946
- /* These commands never recurse */
2947
- /* No command-specific prep needed */
2948
- pass = AT_PASS_MISC ;
2949
- break ;
2950
2959
case AT_DropInherit : /* NO INHERIT */
2960
+ case AT_AddOf : /* OF */
2961
+ case AT_DropOf : /* NOT OF */
2951
2962
ATSimplePermissions (rel , ATT_TABLE );
2963
+ /* These commands never recurse */
2952
2964
/* No command-specific prep needed */
2953
2965
pass = AT_PASS_MISC ;
2954
2966
break ;
@@ -3211,6 +3223,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
3211
3223
case AT_DropInherit :
3212
3224
ATExecDropInherit (rel , (RangeVar * ) cmd -> def , lockmode );
3213
3225
break ;
3226
+ case AT_AddOf :
3227
+ ATExecAddOf (rel , (TypeName * ) cmd -> def , lockmode );
3228
+ break ;
3229
+ case AT_DropOf :
3230
+ ATExecDropOf (rel , lockmode );
3231
+ break ;
3214
3232
case AT_GenericOptions :
3215
3233
ATExecGenericOptions (rel , (List * ) cmd -> def );
3216
3234
break ;
@@ -4045,6 +4063,42 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
4045
4063
}
4046
4064
4047
4065
4066
+ /*
4067
+ * check_of_type
4068
+ *
4069
+ * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
4070
+ * isn't suitable, throw an error. Currently, we require that the type
4071
+ * originated with CREATE TABLE AS. We could support any row type, but doing so
4072
+ * would require handling a number of extra corner cases in the DDL commands.
4073
+ */
4074
+ void
4075
+ check_of_type (HeapTuple typetuple )
4076
+ {
4077
+ Form_pg_type typ = (Form_pg_type ) GETSTRUCT (typetuple );
4078
+ bool typeOk = false;
4079
+
4080
+ if (typ -> typtype == TYPTYPE_COMPOSITE )
4081
+ {
4082
+ Relation typeRelation ;
4083
+
4084
+ Assert (OidIsValid (typ -> typrelid ));
4085
+ typeRelation = relation_open (typ -> typrelid , AccessShareLock );
4086
+ typeOk = (typeRelation -> rd_rel -> relkind == RELKIND_COMPOSITE_TYPE );
4087
+ /*
4088
+ * Close the parent rel, but keep our AccessShareLock on it until xact
4089
+ * commit. That will prevent someone else from deleting or ALTERing
4090
+ * the type before the typed table creation/conversion commits.
4091
+ */
4092
+ relation_close (typeRelation , NoLock );
4093
+ }
4094
+ if (!typeOk )
4095
+ ereport (ERROR ,
4096
+ (errcode (ERRCODE_WRONG_OBJECT_TYPE ),
4097
+ errmsg ("type %s is not a composite type" ,
4098
+ format_type_be (HeapTupleGetOid (typetuple )))));
4099
+ }
4100
+
4101
+
4048
4102
/*
4049
4103
* ALTER TABLE ADD COLUMN
4050
4104
*
@@ -8355,8 +8409,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
8355
8409
ScanKeyData key [3 ];
8356
8410
HeapTuple inheritsTuple ,
8357
8411
attributeTuple ,
8358
- constraintTuple ,
8359
- depTuple ;
8412
+ constraintTuple ;
8360
8413
List * connames ;
8361
8414
bool found = false;
8362
8415
@@ -8522,11 +8575,29 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
8522
8575
systable_endscan (scan );
8523
8576
heap_close (catalogRelation , RowExclusiveLock );
8524
8577
8525
- /*
8526
- * Drop the dependency
8527
- *
8528
- * There's no convenient way to do this, so go trawling through pg_depend
8529
- */
8578
+ drop_parent_dependency (RelationGetRelid (rel ),
8579
+ RelationRelationId ,
8580
+ RelationGetRelid (parent_rel ));
8581
+
8582
+ /* keep our lock on the parent relation until commit */
8583
+ heap_close (parent_rel , NoLock );
8584
+ }
8585
+
8586
+ /*
8587
+ * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
8588
+ * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
8589
+ * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
8590
+ * be TypeRelationId). There's no convenient way to do this, so go trawling
8591
+ * through pg_depend.
8592
+ */
8593
+ static void
8594
+ drop_parent_dependency (Oid relid , Oid refclassid , Oid refobjid )
8595
+ {
8596
+ Relation catalogRelation ;
8597
+ SysScanDesc scan ;
8598
+ ScanKeyData key [3 ];
8599
+ HeapTuple depTuple ;
8600
+
8530
8601
catalogRelation = heap_open (DependRelationId , RowExclusiveLock );
8531
8602
8532
8603
ScanKeyInit (& key [0 ],
@@ -8536,7 +8607,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
8536
8607
ScanKeyInit (& key [1 ],
8537
8608
Anum_pg_depend_objid ,
8538
8609
BTEqualStrategyNumber , F_OIDEQ ,
8539
- ObjectIdGetDatum (RelationGetRelid ( rel ) ));
8610
+ ObjectIdGetDatum (relid ));
8540
8611
ScanKeyInit (& key [2 ],
8541
8612
Anum_pg_depend_objsubid ,
8542
8613
BTEqualStrategyNumber , F_INT4EQ ,
@@ -8549,18 +8620,190 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
8549
8620
{
8550
8621
Form_pg_depend dep = (Form_pg_depend ) GETSTRUCT (depTuple );
8551
8622
8552
- if (dep -> refclassid == RelationRelationId &&
8553
- dep -> refobjid == RelationGetRelid ( parent_rel ) &&
8623
+ if (dep -> refclassid == refclassid &&
8624
+ dep -> refobjid == refobjid &&
8554
8625
dep -> refobjsubid == 0 &&
8555
8626
dep -> deptype == DEPENDENCY_NORMAL )
8556
8627
simple_heap_delete (catalogRelation , & depTuple -> t_self );
8557
8628
}
8558
8629
8559
8630
systable_endscan (scan );
8560
8631
heap_close (catalogRelation , RowExclusiveLock );
8632
+ }
8561
8633
8562
- /* keep our lock on the parent relation until commit */
8563
- heap_close (parent_rel , NoLock );
8634
+ /*
8635
+ * ALTER TABLE OF
8636
+ *
8637
+ * Attach a table to a composite type, as though it had been created with CREATE
8638
+ * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
8639
+ * subject table must not have inheritance parents. These restrictions ensure
8640
+ * that you cannot create a configuration impossible with CREATE TABLE OF alone.
8641
+ */
8642
+ static void
8643
+ ATExecAddOf (Relation rel , const TypeName * ofTypename , LOCKMODE lockmode )
8644
+ {
8645
+ Oid relid = RelationGetRelid (rel );
8646
+ Type typetuple ;
8647
+ Form_pg_type typ ;
8648
+ Oid typeid ;
8649
+ Relation inheritsRelation ,
8650
+ relationRelation ;
8651
+ SysScanDesc scan ;
8652
+ ScanKeyData key ;
8653
+ AttrNumber table_attno ,
8654
+ type_attno ;
8655
+ TupleDesc typeTupleDesc ,
8656
+ tableTupleDesc ;
8657
+ ObjectAddress tableobj ,
8658
+ typeobj ;
8659
+ HeapTuple classtuple ;
8660
+
8661
+ /* Validate the type. */
8662
+ typetuple = typenameType (NULL , ofTypename , NULL );
8663
+ check_of_type (typetuple );
8664
+ typ = (Form_pg_type ) GETSTRUCT (typetuple );
8665
+ typeid = HeapTupleGetOid (typetuple );
8666
+
8667
+ /* Fail if the table has any inheritance parents. */
8668
+ inheritsRelation = heap_open (InheritsRelationId , AccessShareLock );
8669
+ ScanKeyInit (& key ,
8670
+ Anum_pg_inherits_inhrelid ,
8671
+ BTEqualStrategyNumber , F_OIDEQ ,
8672
+ ObjectIdGetDatum (relid ));
8673
+ scan = systable_beginscan (inheritsRelation , InheritsRelidSeqnoIndexId ,
8674
+ true, SnapshotNow , 1 , & key );
8675
+ if (HeapTupleIsValid (systable_getnext (scan )))
8676
+ ereport (ERROR ,
8677
+ (errcode (ERRCODE_WRONG_OBJECT_TYPE ),
8678
+ errmsg ("typed tables cannot inherit" )));
8679
+ systable_endscan (scan );
8680
+ heap_close (inheritsRelation , AccessShareLock );
8681
+
8682
+ /*
8683
+ * Check the tuple descriptors for compatibility. Unlike inheritance, we
8684
+ * require that the order also match. However, attnotnull need not match.
8685
+ * Also unlike inheritance, we do not require matching relhasoids.
8686
+ */
8687
+ typeTupleDesc = lookup_rowtype_tupdesc (typeid , -1 );
8688
+ tableTupleDesc = RelationGetDescr (rel );
8689
+ table_attno = 1 ;
8690
+ for (type_attno = 1 ; type_attno <= typeTupleDesc -> natts ; type_attno ++ )
8691
+ {
8692
+ Form_pg_attribute type_attr ,
8693
+ table_attr ;
8694
+ const char * type_attname ,
8695
+ * table_attname ;
8696
+
8697
+ /* Get the next non-dropped type attribute. */
8698
+ type_attr = typeTupleDesc -> attrs [type_attno - 1 ];
8699
+ if (type_attr -> attisdropped )
8700
+ continue ;
8701
+ type_attname = NameStr (type_attr -> attname );
8702
+
8703
+ /* Get the next non-dropped table attribute. */
8704
+ do
8705
+ {
8706
+ if (table_attno > tableTupleDesc -> natts )
8707
+ ereport (ERROR ,
8708
+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
8709
+ errmsg ("table is missing column \"%s\"" ,
8710
+ type_attname )));
8711
+ table_attr = tableTupleDesc -> attrs [table_attno ++ - 1 ];
8712
+ } while (table_attr -> attisdropped );
8713
+ table_attname = NameStr (table_attr -> attname );
8714
+
8715
+ /* Compare name. */
8716
+ if (strncmp (table_attname , type_attname , NAMEDATALEN ) != 0 )
8717
+ ereport (ERROR ,
8718
+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
8719
+ errmsg ("table has column \"%s\" where type requires \"%s\"" ,
8720
+ table_attname , type_attname )));
8721
+
8722
+ /* Compare type. */
8723
+ if (table_attr -> atttypid != type_attr -> atttypid ||
8724
+ table_attr -> atttypmod != type_attr -> atttypmod ||
8725
+ table_attr -> attcollation != type_attr -> attcollation )
8726
+ ereport (ERROR ,
8727
+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
8728
+ errmsg ("table \"%s\" has different type for column \"%s\"" ,
8729
+ RelationGetRelationName (rel ), type_attname )));
8730
+ }
8731
+ DecrTupleDescRefCount (typeTupleDesc );
8732
+
8733
+ /* Any remaining columns at the end of the table had better be dropped. */
8734
+ for (; table_attno <= tableTupleDesc -> natts ; table_attno ++ )
8735
+ {
8736
+ Form_pg_attribute table_attr = tableTupleDesc -> attrs [table_attno - 1 ];
8737
+ if (!table_attr -> attisdropped )
8738
+ ereport (ERROR ,
8739
+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
8740
+ errmsg ("table has extra column \"%s\"" ,
8741
+ NameStr (table_attr -> attname ))));
8742
+ }
8743
+
8744
+ /* If the table was already typed, drop the existing dependency. */
8745
+ if (rel -> rd_rel -> reloftype )
8746
+ drop_parent_dependency (relid , TypeRelationId , rel -> rd_rel -> reloftype );
8747
+
8748
+ /* Record a dependency on the new type. */
8749
+ tableobj .classId = RelationRelationId ;
8750
+ tableobj .objectId = relid ;
8751
+ tableobj .objectSubId = 0 ;
8752
+ typeobj .classId = TypeRelationId ;
8753
+ typeobj .objectId = typeid ;
8754
+ typeobj .objectSubId = 0 ;
8755
+ recordDependencyOn (& tableobj , & typeobj , DEPENDENCY_NORMAL );
8756
+
8757
+ /* Update pg_class.reloftype */
8758
+ relationRelation = heap_open (RelationRelationId , RowExclusiveLock );
8759
+ classtuple = SearchSysCacheCopy1 (RELOID , ObjectIdGetDatum (relid ));
8760
+ if (!HeapTupleIsValid (classtuple ))
8761
+ elog (ERROR , "cache lookup failed for relation %u" , relid );
8762
+ ((Form_pg_class ) GETSTRUCT (classtuple ))-> reloftype = typeid ;
8763
+ simple_heap_update (relationRelation , & classtuple -> t_self , classtuple );
8764
+ CatalogUpdateIndexes (relationRelation , classtuple );
8765
+ heap_freetuple (classtuple );
8766
+ heap_close (relationRelation , RowExclusiveLock );
8767
+
8768
+ ReleaseSysCache (typetuple );
8769
+ }
8770
+
8771
+ /*
8772
+ * ALTER TABLE NOT OF
8773
+ *
8774
+ * Detach a typed table from its originating type. Just clear reloftype and
8775
+ * remove the dependency.
8776
+ */
8777
+ static void
8778
+ ATExecDropOf (Relation rel , LOCKMODE lockmode )
8779
+ {
8780
+ Oid relid = RelationGetRelid (rel );
8781
+ Relation relationRelation ;
8782
+ HeapTuple tuple ;
8783
+
8784
+ if (!OidIsValid (rel -> rd_rel -> reloftype ))
8785
+ ereport (ERROR ,
8786
+ (errcode (ERRCODE_WRONG_OBJECT_TYPE ),
8787
+ errmsg ("\"%s\" is not a typed table" ,
8788
+ RelationGetRelationName (rel ))));
8789
+
8790
+ /*
8791
+ * We don't bother to check ownership of the type --- ownership of the table
8792
+ * is presumed enough rights. No lock required on the type, either.
8793
+ */
8794
+
8795
+ drop_parent_dependency (relid , TypeRelationId , rel -> rd_rel -> reloftype );
8796
+
8797
+ /* Clear pg_class.reloftype */
8798
+ relationRelation = heap_open (RelationRelationId , RowExclusiveLock );
8799
+ tuple = SearchSysCacheCopy1 (RELOID , ObjectIdGetDatum (relid ));
8800
+ if (!HeapTupleIsValid (tuple ))
8801
+ elog (ERROR , "cache lookup failed for relation %u" , relid );
8802
+ ((Form_pg_class ) GETSTRUCT (tuple ))-> reloftype = InvalidOid ;
8803
+ simple_heap_update (relationRelation , & tuple -> t_self , tuple );
8804
+ CatalogUpdateIndexes (relationRelation , tuple );
8805
+ heap_freetuple (tuple );
8806
+ heap_close (relationRelation , RowExclusiveLock );
8564
8807
}
8565
8808
8566
8809
/*
0 commit comments