mirror of
https://github.com/postgres/postgres.git
synced 2025-11-06 07:49:08 +03:00
Implement table partitioning.
Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
This commit is contained in:
@@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
|
||||
* locked the relation.
|
||||
*/
|
||||
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
|
||||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
|
||||
onerel->rd_rel->relkind == RELKIND_MATVIEW ||
|
||||
onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
{
|
||||
/* Regular table, so we'll use the regular row acquisition function */
|
||||
acquirefunc = acquire_sample_rows;
|
||||
@@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
|
||||
|
||||
/* Check table type (MATVIEW can't happen, but might as well allow) */
|
||||
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
|
||||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
|
||||
childrel->rd_rel->relkind == RELKIND_MATVIEW ||
|
||||
childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
{
|
||||
/* Regular table, so use the regular row acquisition function */
|
||||
acquirefunc = acquire_sample_rows;
|
||||
|
||||
@@ -161,6 +161,11 @@ typedef struct CopyStateData
|
||||
ExprState **defexprs; /* array of default att expressions */
|
||||
bool volatile_defexprs; /* is any of defexprs volatile? */
|
||||
List *range_table;
|
||||
PartitionDispatch *partition_dispatch_info;
|
||||
int num_dispatch;
|
||||
int num_partitions;
|
||||
ResultRelInfo *partitions;
|
||||
TupleConversionMap **partition_tupconv_maps;
|
||||
|
||||
/*
|
||||
* These variables are used to reduce overhead in textual COPY FROM.
|
||||
@@ -1397,6 +1402,71 @@ BeginCopy(ParseState *pstate,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("table \"%s\" does not have OIDs",
|
||||
RelationGetRelationName(cstate->rel))));
|
||||
|
||||
/*
|
||||
* Initialize state for CopyFrom tuple routing. Watch out for
|
||||
* any foreign partitions.
|
||||
*/
|
||||
if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
{
|
||||
PartitionDispatch *pd;
|
||||
List *leaf_parts;
|
||||
ListCell *cell;
|
||||
int i,
|
||||
num_parted,
|
||||
num_leaf_parts;
|
||||
ResultRelInfo *leaf_part_rri;
|
||||
|
||||
/* Get the tuple-routing information and lock partitions */
|
||||
pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
|
||||
&num_parted, &leaf_parts);
|
||||
num_leaf_parts = list_length(leaf_parts);
|
||||
cstate->partition_dispatch_info = pd;
|
||||
cstate->num_dispatch = num_parted;
|
||||
cstate->num_partitions = num_leaf_parts;
|
||||
cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
|
||||
sizeof(ResultRelInfo));
|
||||
cstate->partition_tupconv_maps = (TupleConversionMap **)
|
||||
palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
|
||||
|
||||
leaf_part_rri = cstate->partitions;
|
||||
i = 0;
|
||||
foreach(cell, leaf_parts)
|
||||
{
|
||||
Relation partrel;
|
||||
|
||||
/*
|
||||
* We locked all the partitions above including the leaf
|
||||
* partitions. Note that each of the relations in
|
||||
* cstate->partitions will be closed by CopyFrom() after
|
||||
* it's finished with its processing.
|
||||
*/
|
||||
partrel = heap_open(lfirst_oid(cell), NoLock);
|
||||
|
||||
/*
|
||||
* Verify result relation is a valid target for the current
|
||||
* operation.
|
||||
*/
|
||||
CheckValidResultRel(partrel, CMD_INSERT);
|
||||
|
||||
InitResultRelInfo(leaf_part_rri,
|
||||
partrel,
|
||||
1, /* dummy */
|
||||
false, /* no partition constraint check */
|
||||
0);
|
||||
|
||||
/* Open partition indices */
|
||||
ExecOpenIndices(leaf_part_rri, false);
|
||||
|
||||
if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
|
||||
cstate->partition_tupconv_maps[i] =
|
||||
convert_tuples_by_name(tupDesc,
|
||||
RelationGetDescr(partrel),
|
||||
gettext_noop("could not convert row type"));
|
||||
leaf_part_rri++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1751,6 +1821,12 @@ BeginCopyTo(ParseState *pstate,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot copy from sequence \"%s\"",
|
||||
RelationGetRelationName(rel))));
|
||||
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot copy from partitioned table \"%s\"",
|
||||
RelationGetRelationName(rel)),
|
||||
errhint("Try the COPY (SELECT ...) TO variant.")));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
@@ -2249,6 +2325,7 @@ CopyFrom(CopyState cstate)
|
||||
Datum *values;
|
||||
bool *nulls;
|
||||
ResultRelInfo *resultRelInfo;
|
||||
ResultRelInfo *saved_resultRelInfo = NULL;
|
||||
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
|
||||
ExprContext *econtext;
|
||||
TupleTableSlot *myslot;
|
||||
@@ -2275,6 +2352,7 @@ CopyFrom(CopyState cstate)
|
||||
* only hint about them in the view case.)
|
||||
*/
|
||||
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
|
||||
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
|
||||
!(cstate->rel->trigdesc &&
|
||||
cstate->rel->trigdesc->trig_insert_instead_row))
|
||||
{
|
||||
@@ -2385,6 +2463,7 @@ CopyFrom(CopyState cstate)
|
||||
InitResultRelInfo(resultRelInfo,
|
||||
cstate->rel,
|
||||
1, /* dummy rangetable index */
|
||||
true, /* do load partition check expression */
|
||||
0);
|
||||
|
||||
ExecOpenIndices(resultRelInfo, false);
|
||||
@@ -2407,11 +2486,13 @@ CopyFrom(CopyState cstate)
|
||||
* BEFORE/INSTEAD OF triggers, or we need to evaluate volatile default
|
||||
* expressions. Such triggers or expressions might query the table we're
|
||||
* inserting to, and act differently if the tuples that have already been
|
||||
* processed and prepared for insertion are not there.
|
||||
* processed and prepared for insertion are not there. We also can't
|
||||
* do it if the table is partitioned.
|
||||
*/
|
||||
if ((resultRelInfo->ri_TrigDesc != NULL &&
|
||||
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
|
||||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
|
||||
cstate->partition_dispatch_info != NULL ||
|
||||
cstate->volatile_defexprs)
|
||||
{
|
||||
useHeapMultiInsert = false;
|
||||
@@ -2488,6 +2569,59 @@ CopyFrom(CopyState cstate)
|
||||
slot = myslot;
|
||||
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
|
||||
|
||||
/* Determine the partition to heap_insert the tuple into */
|
||||
if (cstate->partition_dispatch_info)
|
||||
{
|
||||
int leaf_part_index;
|
||||
TupleConversionMap *map;
|
||||
|
||||
/*
|
||||
* Away we go ... If we end up not finding a partition after all,
|
||||
* ExecFindPartition() does not return and errors out instead.
|
||||
* Otherwise, the returned value is to be used as an index into
|
||||
* arrays mt_partitions[] and mt_partition_tupconv_maps[] that
|
||||
* will get us the ResultRelInfo and TupleConversionMap for the
|
||||
* partition, respectively.
|
||||
*/
|
||||
leaf_part_index = ExecFindPartition(resultRelInfo,
|
||||
cstate->partition_dispatch_info,
|
||||
slot,
|
||||
estate);
|
||||
Assert(leaf_part_index >= 0 &&
|
||||
leaf_part_index < cstate->num_partitions);
|
||||
|
||||
/*
|
||||
* Save the old ResultRelInfo and switch to the one corresponding
|
||||
* to the selected partition.
|
||||
*/
|
||||
saved_resultRelInfo = resultRelInfo;
|
||||
resultRelInfo = cstate->partitions + leaf_part_index;
|
||||
|
||||
/* We do not yet have a way to insert into a foreign partition */
|
||||
if (resultRelInfo->ri_FdwRoutine)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot route inserted tuples to a foreign table")));
|
||||
|
||||
/*
|
||||
* For ExecInsertIndexTuples() to work on the partition's indexes
|
||||
*/
|
||||
estate->es_result_relation_info = resultRelInfo;
|
||||
|
||||
/*
|
||||
* We might need to convert from the parent rowtype to the
|
||||
* partition rowtype.
|
||||
*/
|
||||
map = cstate->partition_tupconv_maps[leaf_part_index];
|
||||
if (map)
|
||||
{
|
||||
tuple = do_convert_tuple(tuple, map);
|
||||
ExecStoreTuple(tuple, slot, InvalidBuffer, true);
|
||||
}
|
||||
|
||||
tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
|
||||
}
|
||||
|
||||
skip_tuple = false;
|
||||
|
||||
/* BEFORE ROW INSERT Triggers */
|
||||
@@ -2513,7 +2647,8 @@ CopyFrom(CopyState cstate)
|
||||
else
|
||||
{
|
||||
/* Check the constraints of the tuple */
|
||||
if (cstate->rel->rd_att->constr)
|
||||
if (cstate->rel->rd_att->constr ||
|
||||
resultRelInfo->ri_PartitionCheck)
|
||||
ExecConstraints(resultRelInfo, slot, estate);
|
||||
|
||||
if (useHeapMultiInsert)
|
||||
@@ -2546,7 +2681,8 @@ CopyFrom(CopyState cstate)
|
||||
List *recheckIndexes = NIL;
|
||||
|
||||
/* OK, store the tuple and create index entries for it */
|
||||
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
|
||||
heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
|
||||
hi_options, bistate);
|
||||
|
||||
if (resultRelInfo->ri_NumIndices > 0)
|
||||
recheckIndexes = ExecInsertIndexTuples(slot,
|
||||
@@ -2570,6 +2706,12 @@ CopyFrom(CopyState cstate)
|
||||
* tuples inserted by an INSERT command.
|
||||
*/
|
||||
processed++;
|
||||
|
||||
if (saved_resultRelInfo)
|
||||
{
|
||||
resultRelInfo = saved_resultRelInfo;
|
||||
estate->es_result_relation_info = resultRelInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2607,6 +2749,32 @@ CopyFrom(CopyState cstate)
|
||||
|
||||
ExecCloseIndices(resultRelInfo);
|
||||
|
||||
/* Close all the partitioned tables, leaf partitions, and their indices */
|
||||
if (cstate->partition_dispatch_info)
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Remember cstate->partition_dispatch_info[0] corresponds to the root
|
||||
* partitioned table, which we must not try to close, because it is
|
||||
* the main target table of COPY that will be closed eventually by
|
||||
* DoCopy().
|
||||
*/
|
||||
for (i = 1; i < cstate->num_dispatch; i++)
|
||||
{
|
||||
PartitionDispatch pd = cstate->partition_dispatch_info[i];
|
||||
|
||||
heap_close(pd->reldesc, NoLock);
|
||||
}
|
||||
for (i = 0; i < cstate->num_partitions; i++)
|
||||
{
|
||||
ResultRelInfo *resultRelInfo = cstate->partitions + i;
|
||||
|
||||
ExecCloseIndices(resultRelInfo);
|
||||
heap_close(resultRelInfo->ri_RelationDesc, NoLock);
|
||||
}
|
||||
}
|
||||
|
||||
FreeExecutorState(estate);
|
||||
|
||||
/*
|
||||
|
||||
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
|
||||
* Create the relation. (This will error out if there's an existing view,
|
||||
* so we don't need more code to complain if "replace" is false.)
|
||||
*/
|
||||
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
|
||||
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
|
||||
|
||||
/*
|
||||
* If necessary, create a TOAST table for the target table. Note that
|
||||
|
||||
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
char *accessMethodName, Oid accessMethodId,
|
||||
bool amcanorder,
|
||||
bool isconstraint);
|
||||
static Oid GetIndexOpClass(List *opclass, Oid attrType,
|
||||
char *accessMethodName, Oid accessMethodId);
|
||||
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
|
||||
List *colnames, List *exclusionOpNames,
|
||||
bool primary, bool isconstraint);
|
||||
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot create index on foreign table \"%s\"",
|
||||
RelationGetRelationName(rel))));
|
||||
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot create index on partitioned table \"%s\"",
|
||||
RelationGetRelationName(rel))));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
/*
|
||||
* Identify the opclass to use.
|
||||
*/
|
||||
classOidP[attn] = GetIndexOpClass(attribute->opclass,
|
||||
atttype,
|
||||
accessMethodName,
|
||||
accessMethodId);
|
||||
classOidP[attn] = ResolveOpClass(attribute->opclass,
|
||||
atttype,
|
||||
accessMethodName,
|
||||
accessMethodId);
|
||||
|
||||
/*
|
||||
* Identify the exclusion operator, if any.
|
||||
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
|
||||
/*
|
||||
* Resolve possibly-defaulted operator class specification
|
||||
*
|
||||
* Note: This is used to resolve operator class specification in index and
|
||||
* partition key definitions.
|
||||
*/
|
||||
static Oid
|
||||
GetIndexOpClass(List *opclass, Oid attrType,
|
||||
char *accessMethodName, Oid accessMethodId)
|
||||
Oid
|
||||
ResolveOpClass(List *opclass, Oid attrType,
|
||||
char *accessMethodName, Oid accessMethodId)
|
||||
{
|
||||
char *schemaname;
|
||||
char *opcname;
|
||||
|
||||
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
|
||||
* check */
|
||||
|
||||
/* Currently, we only allow plain tables to be locked */
|
||||
if (relkind != RELKIND_RELATION)
|
||||
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table",
|
||||
|
||||
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
|
||||
rv->relname)));
|
||||
|
||||
/* Relation type MUST be a table. */
|
||||
if (relkind != RELKIND_RELATION)
|
||||
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table", rv->relname)));
|
||||
@@ -384,7 +384,8 @@ RemovePolicyById(Oid policy_id)
|
||||
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
|
||||
|
||||
rel = heap_open(relid, AccessExclusiveLock);
|
||||
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
||||
if (rel->rd_rel->relkind != RELKIND_RELATION &&
|
||||
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table",
|
||||
|
||||
@@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
|
||||
relation->rd_rel->relkind != RELKIND_VIEW &&
|
||||
relation->rd_rel->relkind != RELKIND_MATVIEW &&
|
||||
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
|
||||
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
|
||||
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
|
||||
relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
|
||||
|
||||
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
|
||||
stmt->tablespacename = NULL;
|
||||
stmt->if_not_exists = seq->if_not_exists;
|
||||
|
||||
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
|
||||
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
|
||||
seqoid = address.objectId;
|
||||
Assert(seqoid != InvalidOid);
|
||||
|
||||
@@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by)
|
||||
|
||||
/* Must be a regular or foreign table */
|
||||
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
|
||||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
|
||||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
|
||||
tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("referenced relation \"%s\" is not a table or foreign table",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
|
||||
* Triggers must be on tables or views, and there are additional
|
||||
* relation-type-specific restrictions.
|
||||
*/
|
||||
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
||||
if (rel->rd_rel->relkind == RELKIND_RELATION ||
|
||||
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
{
|
||||
/* Tables can't have INSTEAD OF triggers */
|
||||
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
|
||||
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
|
||||
errmsg("\"%s\" is a table",
|
||||
RelationGetRelationName(rel)),
|
||||
errdetail("Tables cannot have INSTEAD OF triggers.")));
|
||||
/* Disallow ROW triggers on partitioned tables */
|
||||
if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is a partitioned table",
|
||||
RelationGetRelationName(rel)),
|
||||
errdetail("Partitioned tables cannot have ROW triggers.")));
|
||||
}
|
||||
else if (rel->rd_rel->relkind == RELKIND_VIEW)
|
||||
{
|
||||
@@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid)
|
||||
|
||||
if (rel->rd_rel->relkind != RELKIND_RELATION &&
|
||||
rel->rd_rel->relkind != RELKIND_VIEW &&
|
||||
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
|
||||
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
|
||||
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table, view, or foreign table",
|
||||
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
|
||||
|
||||
/* only tables and views can have triggers */
|
||||
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
|
||||
form->relkind != RELKIND_FOREIGN_TABLE)
|
||||
form->relkind != RELKIND_FOREIGN_TABLE &&
|
||||
form->relkind != RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table, view, or foreign table",
|
||||
|
||||
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
|
||||
/*
|
||||
* Finally create the relation. This also creates the type.
|
||||
*/
|
||||
DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
|
||||
DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
|
||||
NULL);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
@@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
|
||||
*/
|
||||
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
|
||||
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
|
||||
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
|
||||
onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
|
||||
onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
||||
{
|
||||
ereport(WARNING,
|
||||
(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
|
||||
|
||||
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
|
||||
* existing view, so we don't need more code to complain if "replace"
|
||||
* is false).
|
||||
*/
|
||||
address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
|
||||
address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
|
||||
NULL);
|
||||
Assert(address.objectId != InvalidOid);
|
||||
return address;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user