mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
Centralize executor-related partitioning code.
Some code is moved from partition.c, which has grown very quickly lately; splitting the executor parts out might help to keep it from getting totally out of control. Other code is moved from execMain.c. All is moved to a new file execPartition.c. get_partition_for_tuple now has a new interface that more clearly separates executor concerns from generic concerns. Amit Langote. A slight comment tweak by me. Discussion: http://postgr.es/m/1f0985f8-3b61-8bc4-4350-baa6d804cb6d@lab.ntt.co.jp
This commit is contained in:
@ -43,7 +43,6 @@
|
||||
#include "access/xact.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/partition.h"
|
||||
#include "catalog/pg_inherits_fn.h"
|
||||
#include "catalog/pg_publication.h"
|
||||
#include "commands/matview.h"
|
||||
#include "commands/trigger.h"
|
||||
@ -98,14 +97,8 @@ static char *ExecBuildSlotValueDescription(Oid reloid,
|
||||
TupleDesc tupdesc,
|
||||
Bitmapset *modifiedCols,
|
||||
int maxfieldlen);
|
||||
static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
|
||||
Datum *values,
|
||||
bool *isnull,
|
||||
int maxfieldlen);
|
||||
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
|
||||
Plan *planTree);
|
||||
static void ExecPartitionCheck(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot, EState *estate);
|
||||
|
||||
/*
|
||||
* Note that GetUpdatedColumns() also exists in commands/trigger.c. There does
|
||||
@ -1854,8 +1847,10 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
|
||||
|
||||
/*
|
||||
* ExecPartitionCheck --- check that tuple meets the partition constraint.
|
||||
*
|
||||
* Exported in executor.h for outside use.
|
||||
*/
|
||||
static void
|
||||
void
|
||||
ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
|
||||
EState *estate)
|
||||
{
|
||||
@ -3245,258 +3240,3 @@ EvalPlanQualEnd(EPQState *epqstate)
|
||||
epqstate->planstate = NULL;
|
||||
epqstate->origslot = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecSetupPartitionTupleRouting - set up information needed during
|
||||
* tuple routing for partitioned tables
|
||||
*
|
||||
* Output arguments:
|
||||
* 'pd' receives an array of PartitionDispatch objects with one entry for
|
||||
* every partitioned table in the partition tree
|
||||
* 'partitions' receives an array of ResultRelInfo* objects with one entry for
|
||||
* every leaf partition in the partition tree
|
||||
* 'tup_conv_maps' receives an array of TupleConversionMap objects with one
|
||||
* entry for every leaf partition (required to convert input tuple based
|
||||
* on the root table's rowtype to a leaf partition's rowtype after tuple
|
||||
* routing is done)
|
||||
* 'partition_tuple_slot' receives a standalone TupleTableSlot to be used
|
||||
* to manipulate any given leaf partition's rowtype after that partition
|
||||
* is chosen by tuple-routing.
|
||||
* 'num_parted' receives the number of partitioned tables in the partition
|
||||
* tree (= the number of entries in the 'pd' output array)
|
||||
* 'num_partitions' receives the number of leaf partitions in the partition
|
||||
* tree (= the number of entries in the 'partitions' and 'tup_conv_maps'
|
||||
* output arrays
|
||||
*
|
||||
* Note that all the relations in the partition tree are locked using the
|
||||
* RowExclusiveLock mode upon return from this function.
|
||||
*/
|
||||
void
|
||||
ExecSetupPartitionTupleRouting(Relation rel,
|
||||
Index resultRTindex,
|
||||
EState *estate,
|
||||
PartitionDispatch **pd,
|
||||
ResultRelInfo ***partitions,
|
||||
TupleConversionMap ***tup_conv_maps,
|
||||
TupleTableSlot **partition_tuple_slot,
|
||||
int *num_parted, int *num_partitions)
|
||||
{
|
||||
TupleDesc tupDesc = RelationGetDescr(rel);
|
||||
List *leaf_parts;
|
||||
ListCell *cell;
|
||||
int i;
|
||||
ResultRelInfo *leaf_part_rri;
|
||||
|
||||
/*
|
||||
* Get the information about the partition tree after locking all the
|
||||
* partitions.
|
||||
*/
|
||||
(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
|
||||
*pd = RelationGetPartitionDispatchInfo(rel, num_parted, &leaf_parts);
|
||||
*num_partitions = list_length(leaf_parts);
|
||||
*partitions = (ResultRelInfo **) palloc(*num_partitions *
|
||||
sizeof(ResultRelInfo *));
|
||||
*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
|
||||
sizeof(TupleConversionMap *));
|
||||
|
||||
/*
|
||||
* Initialize an empty slot that will be used to manipulate tuples of any
|
||||
* given partition's rowtype. It is attached to the caller-specified node
|
||||
* (such as ModifyTableState) and released when the node finishes
|
||||
* processing.
|
||||
*/
|
||||
*partition_tuple_slot = MakeTupleTableSlot();
|
||||
|
||||
leaf_part_rri = (ResultRelInfo *) palloc0(*num_partitions *
|
||||
sizeof(ResultRelInfo));
|
||||
i = 0;
|
||||
foreach(cell, leaf_parts)
|
||||
{
|
||||
Relation partrel;
|
||||
TupleDesc part_tupdesc;
|
||||
|
||||
/*
|
||||
* We locked all the partitions above including the leaf partitions.
|
||||
* Note that each of the relations in *partitions are eventually
|
||||
* closed by the caller.
|
||||
*/
|
||||
partrel = heap_open(lfirst_oid(cell), NoLock);
|
||||
part_tupdesc = RelationGetDescr(partrel);
|
||||
|
||||
/*
|
||||
* Save a tuple conversion map to convert a tuple routed to this
|
||||
* partition from the parent's type to the partition's.
|
||||
*/
|
||||
(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
|
||||
gettext_noop("could not convert row type"));
|
||||
|
||||
InitResultRelInfo(leaf_part_rri,
|
||||
partrel,
|
||||
resultRTindex,
|
||||
rel,
|
||||
estate->es_instrument);
|
||||
|
||||
/*
|
||||
* Verify result relation is a valid target for INSERT.
|
||||
*/
|
||||
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
|
||||
|
||||
/*
|
||||
* Open partition indices (remember we do not support ON CONFLICT in
|
||||
* case of partitioned tables, so we do not need support information
|
||||
* for speculative insertion)
|
||||
*/
|
||||
if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
|
||||
leaf_part_rri->ri_IndexRelationDescs == NULL)
|
||||
ExecOpenIndices(leaf_part_rri, false);
|
||||
|
||||
estate->es_leaf_result_relations =
|
||||
lappend(estate->es_leaf_result_relations, leaf_part_rri);
|
||||
|
||||
(*partitions)[i] = leaf_part_rri++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecFindPartition -- Find a leaf partition in the partition tree rooted
|
||||
* at parent, for the heap tuple contained in *slot
|
||||
*
|
||||
* estate must be non-NULL; we'll need it to compute any expressions in the
|
||||
* partition key(s)
|
||||
*
|
||||
* If no leaf partition is found, this routine errors out with the appropriate
|
||||
* error message, else it returns the leaf partition sequence number returned
|
||||
* by get_partition_for_tuple() unchanged.
|
||||
*/
|
||||
int
|
||||
ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
|
||||
TupleTableSlot *slot, EState *estate)
|
||||
{
|
||||
int result;
|
||||
PartitionDispatchData *failed_at;
|
||||
TupleTableSlot *failed_slot;
|
||||
|
||||
/*
|
||||
* First check the root table's partition constraint, if any. No point in
|
||||
* routing the tuple if it doesn't belong in the root table itself.
|
||||
*/
|
||||
if (resultRelInfo->ri_PartitionCheck)
|
||||
ExecPartitionCheck(resultRelInfo, slot, estate);
|
||||
|
||||
result = get_partition_for_tuple(pd, slot, estate,
|
||||
&failed_at, &failed_slot);
|
||||
if (result < 0)
|
||||
{
|
||||
Relation failed_rel;
|
||||
Datum key_values[PARTITION_MAX_KEYS];
|
||||
bool key_isnull[PARTITION_MAX_KEYS];
|
||||
char *val_desc;
|
||||
ExprContext *ecxt = GetPerTupleExprContext(estate);
|
||||
|
||||
failed_rel = failed_at->reldesc;
|
||||
ecxt->ecxt_scantuple = failed_slot;
|
||||
FormPartitionKeyDatum(failed_at, failed_slot, estate,
|
||||
key_values, key_isnull);
|
||||
val_desc = ExecBuildSlotPartitionKeyDescription(failed_rel,
|
||||
key_values,
|
||||
key_isnull,
|
||||
64);
|
||||
Assert(OidIsValid(RelationGetRelid(failed_rel)));
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_CHECK_VIOLATION),
|
||||
errmsg("no partition of relation \"%s\" found for row",
|
||||
RelationGetRelationName(failed_rel)),
|
||||
val_desc ? errdetail("Partition key of the failing row contains %s.", val_desc) : 0));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* BuildSlotPartitionKeyDescription
|
||||
*
|
||||
* This works very much like BuildIndexValueDescription() and is currently
|
||||
* used for building error messages when ExecFindPartition() fails to find
|
||||
* partition for a row.
|
||||
*/
|
||||
static char *
|
||||
ExecBuildSlotPartitionKeyDescription(Relation rel,
|
||||
Datum *values,
|
||||
bool *isnull,
|
||||
int maxfieldlen)
|
||||
{
|
||||
StringInfoData buf;
|
||||
PartitionKey key = RelationGetPartitionKey(rel);
|
||||
int partnatts = get_partition_natts(key);
|
||||
int i;
|
||||
Oid relid = RelationGetRelid(rel);
|
||||
AclResult aclresult;
|
||||
|
||||
if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED)
|
||||
return NULL;
|
||||
|
||||
/* If the user has table-level access, just go build the description. */
|
||||
aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_SELECT);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
{
|
||||
/*
|
||||
* Step through the columns of the partition key and make sure the
|
||||
* user has SELECT rights on all of them.
|
||||
*/
|
||||
for (i = 0; i < partnatts; i++)
|
||||
{
|
||||
AttrNumber attnum = get_partition_col_attnum(key, i);
|
||||
|
||||
/*
|
||||
* If this partition key column is an expression, we return no
|
||||
* detail rather than try to figure out what column(s) the
|
||||
* expression includes and if the user has SELECT rights on them.
|
||||
*/
|
||||
if (attnum == InvalidAttrNumber ||
|
||||
pg_attribute_aclcheck(relid, attnum, GetUserId(),
|
||||
ACL_SELECT) != ACLCHECK_OK)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
initStringInfo(&buf);
|
||||
appendStringInfo(&buf, "(%s) = (",
|
||||
pg_get_partkeydef_columns(relid, true));
|
||||
|
||||
for (i = 0; i < partnatts; i++)
|
||||
{
|
||||
char *val;
|
||||
int vallen;
|
||||
|
||||
if (isnull[i])
|
||||
val = "null";
|
||||
else
|
||||
{
|
||||
Oid foutoid;
|
||||
bool typisvarlena;
|
||||
|
||||
getTypeOutputInfo(get_partition_col_typid(key, i),
|
||||
&foutoid, &typisvarlena);
|
||||
val = OidOutputFunctionCall(foutoid, values[i]);
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
appendStringInfoString(&buf, ", ");
|
||||
|
||||
/* truncate if needed */
|
||||
vallen = strlen(val);
|
||||
if (vallen <= maxfieldlen)
|
||||
appendStringInfoString(&buf, val);
|
||||
else
|
||||
{
|
||||
vallen = pg_mbcliplen(val, vallen, maxfieldlen);
|
||||
appendBinaryStringInfo(&buf, val, vallen);
|
||||
appendStringInfoString(&buf, "...");
|
||||
}
|
||||
}
|
||||
|
||||
appendStringInfoChar(&buf, ')');
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
Reference in New Issue
Block a user