hypopg/hypopg_table.c
2018-04-24 12:41:49 +02:00

3034 lines
81 KiB
C

/*-------------------------------------------------------------------------
*
* hypopg_table.c: Implementation of hypothetical partitioning for PostgreSQL
*
* This file contains all the internal code related to hypothetical partition
* support.
*
* This program is open source, licensed under the PostgreSQL license.
* For license terms, see the LICENSE file.
*
* Copyright (C) 2015-2018: Julien Rouhaud
*
*-------------------------------------------------------------------------
*/
#include <math.h>
#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"
#include "miscadmin.h"
#if PG_VERSION_NUM >= 100000
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_class.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/relation.h"
#include "nodes/nodes.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/predtest.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "parser/parse_utilcmd.h"
#include "rewrite/rewriteManip.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "partitioning/partbounds.h"
#endif
#include "include/hypopg.h"
#include "include/hypopg_table.h"
/*--- Variables exported ---*/
List *hypoTables;
/*--- Functions --- */
PG_FUNCTION_INFO_V1(hypopg_add_partition);
PG_FUNCTION_INFO_V1(hypopg_drop_table);
PG_FUNCTION_INFO_V1(hypopg_partition_table);
PG_FUNCTION_INFO_V1(hypopg_reset_table);
PG_FUNCTION_INFO_V1(hypopg_table);
#if PG_VERSION_NUM >= 100000
static void hypo_addTable(hypoTable *entry);
static List *hypo_find_inheritance_children(hypoTable *parent);
static PartitionDesc hypo_generate_partitiondesc(hypoTable *parent);
static void hypo_generate_partkey(CreateStmt *stmt, Oid parentid,
hypoTable *entry);
static PartitionScheme hypo_find_partition_scheme(PlannerInfo *root,
PartitionKey partkey);
static void hypo_generate_partition_key_exprs(hypoTable *entry,
RelOptInfo *rel);
static PartitionBoundSpec *hypo_get_boundspec(Oid tableid);
static Oid hypo_get_default_partition_oid(Oid parentid);
static char *hypo_get_partbounddef(hypoTable *entry);
static char *hypo_get_partkeydef(hypoTable *entry);
static hypoTable *hypo_newTable(Oid parentid);
static Oid hypo_table_find_parent_entry(hypoTable *entry);
static Oid hypo_table_find_parent_oid(Oid parentid);
static Oid hypo_table_find_root_oid(hypoTable *entry);
static hypoTable *hypo_find_table(Oid tableid);
static bool hypo_table_name_is_hypothetical(const char *name);
static void hypo_table_pfree(hypoTable *entry);
static bool hypo_table_remove(Oid tableid);
static const hypoTable *hypo_table_store_parsetree(CreateStmt *node,
const char *queryString, Oid parentid);
static PartitionBoundSpec *hypo_transformPartitionBound(ParseState *pstate,
hypoTable *parent, PartitionBoundSpec *spec);
static void hypo_partition_table(PlannerInfo *root, RelOptInfo *rel,
hypoTable *entry);
static List *hypo_get_partition_constraints(PlannerInfo *root, RelOptInfo *rel,
hypoTable *parent);
static List *hypo_get_qual_from_partbound(hypoTable *parent, PartitionBoundSpec *spec);
static List *hypo_get_qual_for_hash(hypoTable *parent, PartitionBoundSpec *spec);
static List *hypo_get_qual_for_list(hypoTable *parent, PartitionBoundSpec *spec);
static List *hypo_get_qual_for_range(hypoTable *parent, PartitionBoundSpec *spec,
bool for_default);
#define HYPO_RTI_IS_TAGGED(rti, root) (planner_rt_fetch(rti, root)->security_barrier)
#define HYPO_TAG_RTI(rti, root) (planner_rt_fetch(rti, root)->security_barrier = true)
/* Add an hypoTable to hypoTables */
static void
hypo_addTable(hypoTable *entry)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(HypoMemoryContext);
hypoTables = lappend(hypoTables, entry);
MemoryContextSwitchTo(oldcontext);
}
/*
* Adaptation of find_inheritance_children().
*
* FIXME: simplify the algorithm if we maintain the number of partition per
* parent.
*/
static List *
hypo_find_inheritance_children(hypoTable *parent)
{
List *list = NIL;
ListCell *lc;
Oid inhrelid;
Oid *oidarr;
int maxoids,
numoids,
i;
Assert(CurrentMemoryContext != HypoMemoryContext);
maxoids = 32;
oidarr = (Oid *) palloc(maxoids * sizeof(Oid));
numoids = 0;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (entry->parentid != parent->oid)
continue;
inhrelid = entry->oid;
if (numoids >= maxoids)
{
maxoids *= 2;
oidarr = (Oid *) repalloc(oidarr, maxoids * sizeof(Oid));
}
oidarr[numoids++] = inhrelid;
}
/*
* If we found more than one child, sort them by OID. This ensures
* reasonably consistent behavior regardless of the vagaries of an
* indexscan. This is important since we need to be sure all backends
* lock children in the same order to avoid needless deadlocks.
*/
if (numoids > 1)
qsort(oidarr, numoids, sizeof(Oid), oid_cmp);
/*
* Bild the result list.
*/
for (i = 0; i < numoids; i++)
{
inhrelid = oidarr[i];
list = lappend_oid(list, inhrelid);
}
pfree(oidarr);
return list;
}
/*
* Given a (root) hypothetically partitioned table, generate the
* PartitionBoundInfo data corresponding to the declared hypothetical
* partitions. Caller is reponsible of providing the right MemoryContext,
* however HypoMemoryContext is not allowed. This is heavily inspired on
* RelationBuildPartitionDesc().
*/
static PartitionDesc
hypo_generate_partitiondesc(hypoTable *parent)
{
List *inhoids,
*partoids;
Oid *oids = NULL;
List *boundspecs = NIL;
ListCell *cell;
int i,
nparts;
PartitionKey key = parent->partkey;
PartitionDesc result;
int ndatums = 0;
int default_index = -1;
/* Hash partitioning specific */
PartitionHashBound **hbounds = NULL;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
Assert(CurrentMemoryContext != HypoMemoryContext);
if (key == NULL)
return NULL;
/* Get partition oids from pg_inherits */
inhoids = hypo_find_inheritance_children(parent);
/* Collect bound spec nodes in a list */
i = 0;
partoids = NIL;
foreach(cell, inhoids)
{
Oid inhrelid = lfirst_oid(cell);
Node *boundspec;
boundspec = (Node *) hypo_get_boundspec(inhrelid);
/*
* Sanity check: If the PartitionBoundSpec says this is the default
* partition, its OID should correspond to whatever's stored in
* pg_partitioned_table.partdefid; if not, the catalog is corrupt.
*/
if (castNode(PartitionBoundSpec, boundspec)->is_default)
{
Oid partdefid;
partdefid = hypo_get_default_partition_oid(parent->oid);
if (partdefid != inhrelid)
elog(ERROR, "expected partdefid %u, but got %u",
inhrelid, partdefid);
}
boundspecs = lappend(boundspecs, boundspec);
partoids = lappend_oid(partoids, inhrelid);
}
nparts = list_length(partoids);
if (nparts > 0)
{
oids = (Oid *) palloc(nparts * sizeof(Oid));
i = 0;
foreach(cell, partoids)
oids[i++] = lfirst_oid(cell);
/* Convert from node to the internal representation */
if (key->strategy == PARTITION_STRATEGY_HASH)
{
ndatums = nparts;
hbounds = (PartitionHashBound **)
palloc(nparts * sizeof(PartitionHashBound *));
i = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
lfirst(cell));
if (spec->strategy != PARTITION_STRATEGY_HASH)
elog(ERROR, "invalid strategy in partition bound spec");
hbounds[i] = (PartitionHashBound *)
palloc(sizeof(PartitionHashBound));
hbounds[i]->modulus = spec->modulus;
hbounds[i]->remainder = spec->remainder;
hbounds[i]->index = i;
i++;
}
/* Sort all the bounds in ascending order */
qsort(hbounds, nparts, sizeof(PartitionHashBound *),
qsort_partition_hbound_cmp);
}
else if (key->strategy == PARTITION_STRATEGY_LIST)
{
List *non_null_values = NIL;
/*
* Create a unified list of non-null values across all partitions.
*/
i = 0;
null_index = -1;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
lfirst(cell));
ListCell *c;
if (spec->strategy != PARTITION_STRATEGY_LIST)
elog(ERROR, "invalid strategy in partition bound spec");
/*
* Note the index of the partition bound spec for the default
* partition. There's no datum to add to the list of non-null
* datums for this partition.
*/
if (spec->is_default)
{
default_index = i;
i++;
continue;
}
foreach(c, spec->listdatums)
{
Const *val = castNode(Const, lfirst(c));
PartitionListValue *list_value = NULL;
if (!val->constisnull)
{
list_value = (PartitionListValue *)
palloc0(sizeof(PartitionListValue));
list_value->index = i;
list_value->value = val->constvalue;
}
else
{
/*
* Never put a null into the values array, flag
* instead for the code further down below where we
* construct the actual relcache struct.
*/
if (null_index != -1)
elog(ERROR, "found null more than once");
null_index = i;
}
if (list_value)
non_null_values = lappend(non_null_values,
list_value);
}
i++;
}
ndatums = list_length(non_null_values);
/*
* Collect all list values in one array. Alongside the value, we
* also save the index of partition the value comes from.
*/
all_values = (PartitionListValue **) palloc(ndatums *
sizeof(PartitionListValue *));
i = 0;
foreach(cell, non_null_values)
{
PartitionListValue *src = lfirst(cell);
all_values[i] = (PartitionListValue *)
palloc(sizeof(PartitionListValue));
all_values[i]->value = src->value;
all_values[i]->index = src->index;
i++;
}
qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
qsort_partition_list_value_cmp, (void *) key);
}
else if (key->strategy == PARTITION_STRATEGY_RANGE)
{
int k;
PartitionRangeBound **all_bounds,
*prev;
all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
sizeof(PartitionRangeBound *));
/*
* Create a unified list of range bounds across all the
* partitions.
*/
i = ndatums = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
lfirst(cell));
PartitionRangeBound *lower,
*upper;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
/*
* Note the index of the partition bound spec for the default
* partition. There's no datum to add to the allbounds array
* for this partition.
*/
if (spec->is_default)
{
default_index = i++;
continue;
}
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
false);
all_bounds[ndatums++] = lower;
all_bounds[ndatums++] = upper;
i++;
}
Assert(ndatums == nparts * 2 ||
(default_index != -1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
/* Save distinct bounds from all_bounds into rbounds. */
rbounds = (PartitionRangeBound **)
palloc(ndatums * sizeof(PartitionRangeBound *));
k = 0;
prev = NULL;
for (i = 0; i < ndatums; i++)
{
PartitionRangeBound *cur = all_bounds[i];
bool is_distinct = false;
int j;
/* Is the current bound distinct from the previous one? */
for (j = 0; j < key->partnatts; j++)
{
Datum cmpval;
if (prev == NULL || cur->kind[j] != prev->kind[j])
{
is_distinct = true;
break;
}
/*
* If the bounds are both MINVALUE or MAXVALUE, stop now
* and treat them as equal, since any values after this
* point must be ignored.
*/
if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
break;
cmpval = FunctionCall2Coll(&key->partsupfunc[j],
key->partcollation[j],
cur->datums[j],
prev->datums[j]);
if (DatumGetInt32(cmpval) != 0)
{
is_distinct = true;
break;
}
}
/*
* Only if the bound is distinct save it into a temporary
* array i.e. rbounds which is later copied into boundinfo
* datums array.
*/
if (is_distinct)
rbounds[k++] = all_bounds[i];
prev = cur;
}
/* Update ndatums to hold the count of distinct datums. */
ndatums = k;
}
else
elog(ERROR, "unexpected partition strategy: %d",
(int) key->strategy);
}
result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
result->nparts = nparts;
if (nparts > 0)
{
PartitionBoundInfo boundinfo;
int *mapping;
int next_index = 0;
result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
boundinfo = (PartitionBoundInfoData *)
palloc0(sizeof(PartitionBoundInfoData));
boundinfo->strategy = key->strategy;
boundinfo->default_index = -1;
boundinfo->ndatums = ndatums;
boundinfo->null_index = -1;
boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
/* Initialize mapping array with invalid values */
mapping = (int *) palloc(sizeof(int) * nparts);
for (i = 0; i < nparts; i++)
mapping[i] = -1;
switch (key->strategy)
{
case PARTITION_STRATEGY_HASH:
{
/* Modulus are stored in ascending order */
int greatest_modulus = hbounds[ndatums - 1]->modulus;
boundinfo->indexes = (int *) palloc(greatest_modulus *
sizeof(int));
for (i = 0; i < greatest_modulus; i++)
boundinfo->indexes[i] = -1;
for (i = 0; i < nparts; i++)
{
int modulus = hbounds[i]->modulus;
int remainder = hbounds[i]->remainder;
boundinfo->datums[i] = (Datum *) palloc(2 *
sizeof(Datum));
boundinfo->datums[i][0] = Int32GetDatum(modulus);
boundinfo->datums[i][1] = Int32GetDatum(remainder);
while (remainder < greatest_modulus)
{
/* overlap? */
Assert(boundinfo->indexes[remainder] == -1);
boundinfo->indexes[remainder] = i;
remainder += modulus;
}
mapping[hbounds[i]->index] = i;
pfree(hbounds[i]);
}
pfree(hbounds);
break;
}
case PARTITION_STRATEGY_LIST:
{
boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
/*
* Copy values. Indexes of individual values are mapped
* to canonical values so that they match for any two list
* partitioned tables with same number of partitions and
* same lists per partition. One way to canonicalize is
* to assign the index in all_values[] of the smallest
* value of each partition, as the index of all of the
* partition's values.
*/
for (i = 0; i < ndatums; i++)
{
boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
key->parttypbyval[0],
key->parttyplen[0]);
/* If the old index has no mapping, assign one */
if (mapping[all_values[i]->index] == -1)
mapping[all_values[i]->index] = next_index++;
boundinfo->indexes[i] = mapping[all_values[i]->index];
}
/*
* If null-accepting partition has no mapped index yet,
* assign one. This could happen if such partition
* accepts only null and hence not covered in the above
* loop which only handled non-null values.
*/
if (null_index != -1)
{
Assert(null_index >= 0);
if (mapping[null_index] == -1)
mapping[null_index] = next_index++;
boundinfo->null_index = mapping[null_index];
}
/* Assign mapped index for the default partition. */
if (default_index != -1)
{
/*
* The default partition accepts any value not
* specified in the lists of other partitions, hence
* it should not get mapped index while assigning
* those for non-null datums.
*/
Assert(default_index >= 0 &&
mapping[default_index] == -1);
mapping[default_index] = next_index++;
boundinfo->default_index = mapping[default_index];
}
/* All partition must now have a valid mapping */
Assert(next_index == nparts);
break;
}
case PARTITION_STRATEGY_RANGE:
{
boundinfo->kind = (PartitionRangeDatumKind **)
palloc(ndatums *
sizeof(PartitionRangeDatumKind *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
for (i = 0; i < ndatums; i++)
{
int j;
boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
sizeof(Datum));
boundinfo->kind[i] = (PartitionRangeDatumKind *)
palloc(key->partnatts *
sizeof(PartitionRangeDatumKind));
for (j = 0; j < key->partnatts; j++)
{
if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
boundinfo->datums[i][j] =
datumCopy(rbounds[i]->datums[j],
key->parttypbyval[j],
key->parttyplen[j]);
boundinfo->kind[i][j] = rbounds[i]->kind[j];
}
/*
* There is no mapping for invalid indexes.
*
* Any lower bounds in the rbounds array have invalid
* indexes assigned, because the values between the
* previous bound (if there is one) and this (lower)
* bound are not part of the range of any existing
* partition.
*/
if (rbounds[i]->lower)
boundinfo->indexes[i] = -1;
else
{
int orig_index = rbounds[i]->index;
/* If the old index has no mapping, assign one */
if (mapping[orig_index] == -1)
mapping[orig_index] = next_index++;
boundinfo->indexes[i] = mapping[orig_index];
}
}
/* Assign mapped index for the default partition. */
if (default_index != -1)
{
Assert(default_index >= 0 && mapping[default_index] == -1);
mapping[default_index] = next_index++;
boundinfo->default_index = mapping[default_index];
}
boundinfo->indexes[i] = -1;
break;
}
default:
elog(ERROR, "unexpected partition strategy: %d",
(int) key->strategy);
}
result->boundinfo = boundinfo;
/*
* Now assign OIDs from the original array into mapped indexes of the
* result array. Order of OIDs in the former is defined by the
* catalog scan that retrieved them, whereas that in the latter is
* defined by canonicalized representation of the partition bounds.
*/
for (i = 0; i < nparts; i++)
result->oids[mapping[i]] = oids[i];
pfree(mapping);
}
return result;
}
/*
* Given a CreateStmt, generate a PartitionKey corresponding to the provided
* PartitionSpec clause, and also store each key's opclass as it's needed for
* the deparsing. This is heavily inspired on DefineRelation() and
* RelationBuildPartitionKey().
*/
static void
hypo_generate_partkey(CreateStmt *stmt, Oid parentid, hypoTable *entry)
{
char strategy;
int partnatts;
AttrNumber partattrs[PARTITION_MAX_KEYS];
Oid partopclass[PARTITION_MAX_KEYS];
Oid partcollation[PARTITION_MAX_KEYS];
List *partexprs = NIL;
Relation rel;
PartitionKey key;
int i;
MemoryContext oldcontext;
Assert(stmt->partspec);
oldcontext = MemoryContextSwitchTo(HypoMemoryContext);
key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
MemoryContextSwitchTo(oldcontext);
rel = heap_open(parentid, AccessShareLock);
/*--- Adapted from DefineRelation ---*/
partnatts = list_length(stmt->partspec->partParams);
key->partnatts = partnatts;
/* Protect fixed-size arrays here and in executor */
if (partnatts > PARTITION_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot partition using more than %d columns",
PARTITION_MAX_KEYS)));
/*
* We need to transform the raw parsetrees corresponding to partition
* expressions into executable expression trees. Like column defaults
* and CHECK constraints, we could not have done the transformation
* earlier.
*/
stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
&strategy);
ComputePartitionAttrs(rel, stmt->partspec->partParams,
partattrs, &partexprs, partopclass,
partcollation, strategy);
key->strategy = strategy;
key->partexprs = partexprs;
/*--- Adapted from RelationBuildPartitionKey ---*/
{
ListCell *partexprs_item;
int16 procnum;
oldcontext = MemoryContextSwitchTo(HypoMemoryContext);
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
/* Gather type and collation info as well */
key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->partattrs = (int16 *) palloc0(key->partnatts * sizeof(int16));
entry->partopclass = (Oid *) palloc0(key->partnatts * sizeof(Oid));
MemoryContextSwitchTo(oldcontext);
/* determine support function number to search for */
procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
HASHEXTENDED_PROC : BTORDER_PROC;
/* Copy partattrs and fill other per-attribute info */
memcpy(key->partattrs, partattrs, key->partnatts * sizeof(int16));
partexprs_item = list_head(key->partexprs);
for (i = 0; i < key->partnatts; i++)
{
AttrNumber attno = key->partattrs[i];
HeapTuple opclasstup;
Form_pg_opclass opclassform;
Oid funcid;
/* Collect opfamily information */
opclasstup = SearchSysCache1(CLAOID,
ObjectIdGetDatum(partopclass[i]));
if (!HeapTupleIsValid(opclasstup))
elog(ERROR, "cache lookup failed for opclass %u", partopclass[i]);
opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
key->partopfamily[i] = opclassform->opcfamily;
key->partopcintype[i] = opclassform->opcintype;
/* Get a support function for the specified opfamily and datatypes */
funcid = get_opfamily_proc(opclassform->opcfamily,
opclassform->opcintype,
opclassform->opcintype,
procnum);
if (!OidIsValid(funcid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
NameStr(opclassform->opcname),
(key->strategy == PARTITION_STRATEGY_HASH) ?
"hash" : "btree",
procnum,
format_type_be(opclassform->opcintype))));
fmgr_info(funcid, &key->partsupfunc[i]);
/* Collation */
key->partcollation[i] = partcollation[i];
/* Collect type information */
if (attno != 0)
{
Form_pg_attribute att = TupleDescAttr(rel->rd_att, attno - 1);
key->parttypid[i] = att->atttypid;
key->parttypmod[i] = att->atttypmod;
key->parttypcoll[i] = att->attcollation;
}
else
{
if (partexprs_item == NULL)
elog(ERROR, "wrong number of partition key expressions");
key->parttypid[i] = exprType(lfirst(partexprs_item));
key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
partexprs_item = lnext(partexprs_item);
}
get_typlenbyvalalign(key->parttypid[i],
&key->parttyplen[i],
&key->parttypbyval[i],
&key->parttypalign[i]);
ReleaseSysCache(opclasstup);
}
}
heap_close(rel, AccessShareLock);
/*--------
* Copied in previous loop
key->partattrs = partattrs;
key->partopfamily = partopfamily;
key->partopcintype = partopcintype;
key->partsupfunc = partsupfunc;
key->partcollation = partcollation;
key->parttypid = parttypid;
key->parttypmod = parttypmod
key->parttyplen = parttyplen;
key->parttypbyval = parttypbyval;
key->parttypalign = parttypalign;
key->parttypcoll= parttypcoll;
*/
entry->partkey = key;
memcpy(entry->partopclass, partopclass, sizeof(Oid) * partnatts);
}
/*
*
* Given a PlannerInfo and PartitionKey, find or create a PartitionScheme for
* this relation.
*
* Heavily inspired on find_partition_scheme()
*/
static PartitionScheme
hypo_find_partition_scheme(PlannerInfo *root, PartitionKey partkey)
{
PartitionScheme part_scheme;
int partnatts,
i;
ListCell *lc;
Assert(CurrentMemoryContext != HypoMemoryContext);
/* adapted from plancat.c / find_partition_scheme() */
Assert(partkey != NULL);
partnatts = partkey->partnatts;
foreach(lc, root->part_schemes)
{
part_scheme = lfirst(lc);
/* Match partitioning strategy and number of keys. */
if (partkey->strategy != part_scheme->strategy ||
partnatts != part_scheme->partnatts)
continue;
/* Match partition key type properties. */
if (memcmp(partkey->partopfamily, part_scheme->partopfamily,
sizeof(Oid) * partnatts) != 0 ||
memcmp(partkey->partopcintype, part_scheme->partopcintype,
sizeof(Oid) * partnatts) != 0 ||
memcmp(partkey->partcollation, part_scheme->partcollation,
sizeof(Oid) * partnatts) != 0)
continue;
/*
* Length and byval information should match when partopcintype
* matches.
*/
Assert(memcmp(partkey->parttyplen, part_scheme->parttyplen,
sizeof(int16) * partnatts) == 0);
Assert(memcmp(partkey->parttypbyval, part_scheme->parttypbyval,
sizeof(bool) * partnatts) == 0);
/*
* If partopfamily and partopcintype matched, must have the same
* partition comparison functions. Note that we cannot reliably
* Assert the equality of function structs themselves for they might
* be different across PartitionKey's, so just Assert for the function
* OIDs.
*/
#ifdef USE_ASSERT_CHECKING
for (i = 0; i < partkey->partnatts; i++)
Assert(partkey->partsupfunc[i].fn_oid ==
part_scheme->partsupfunc[i].fn_oid);
#endif
/* Found matching partition scheme. */
return part_scheme;
}
part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
part_scheme->strategy = partkey->strategy;
part_scheme->partnatts = partkey->partnatts;
part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
memcpy(part_scheme->partopfamily, partkey->partopfamily,
sizeof(Oid) * partnatts);
part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
memcpy(part_scheme->partopcintype, partkey->partopcintype,
sizeof(Oid) * partnatts);
part_scheme->partcollation = (Oid *) palloc(sizeof(Oid) * partnatts);
memcpy(part_scheme->partcollation, partkey->partcollation,
sizeof(Oid) * partnatts);
//part_scheme->parttypcoll = (Oid *) palloc(sizeof(Oid) * partnatts);
//memcpy(part_scheme->parttypcoll, partkey->parttypcoll,
// sizeof(Oid) * partnatts);
part_scheme->parttyplen = (int16 *) palloc(sizeof(int16) * partnatts);
memcpy(part_scheme->parttyplen, partkey->parttyplen,
sizeof(int16) * partnatts);
part_scheme->parttypbyval = (bool *) palloc(sizeof(bool) * partnatts);
memcpy(part_scheme->parttypbyval, partkey->parttypbyval,
sizeof(bool) * partnatts);
part_scheme->partsupfunc = (FmgrInfo *)
palloc(sizeof(FmgrInfo) * partnatts);
for (i = 0; i < partnatts; i++)
fmgr_info_copy(&part_scheme->partsupfunc[i], &partkey->partsupfunc[i],
CurrentMemoryContext);
/* Add the partitioning scheme to PlannerInfo. */
root->part_schemes = lappend(root->part_schemes, part_scheme);
return part_scheme;
}
/*
* Given a PartitionKey, fill the PartitionScheme->partexrps struct.
*
* Heavily inspired on set_baserel_partition_key_exprs
*/
static void
hypo_generate_partition_key_exprs(hypoTable *entry, RelOptInfo *rel)
{
PartitionKey partkey = entry->partkey;
int partnatts;
int cnt;
List **partexprs;
ListCell *lc;
Index varno = rel->relid;
Assert(CurrentMemoryContext != HypoMemoryContext);
/* A partitioned table should have a partition key. */
Assert(partkey != NULL);
partnatts = partkey->partnatts;
partexprs = (List **) palloc(sizeof(List *) * partnatts);
lc = list_head(partkey->partexprs);
for (cnt = 0; cnt < partnatts; cnt++)
{
Expr *partexpr;
AttrNumber attno = partkey->partattrs[cnt];
if (attno != InvalidAttrNumber)
{
/* Single column partition key is stored as a Var node. */
Assert(attno > 0);
partexpr = (Expr *) makeVar(varno, attno,
partkey->parttypid[cnt],
partkey->parttypmod[cnt],
partkey->parttypcoll[cnt], 0);
}
else
{
if (lc == NULL)
elog(ERROR, "wrong number of partition key expressions");
/* Re-stamp the expression with given varno. */
partexpr = (Expr *) copyObject(lfirst(lc));
ChangeVarNodes((Node *) partexpr, 1, varno, 0);
lc = lnext(lc);
}
partexprs[cnt] = list_make1(partexpr);
}
rel->partexprs = partexprs;
/*
* A base relation can not have nullable partition key expressions. We
* still allocate array of empty expressions lists to keep partition key
* expression handling code simple. See build_joinrel_partition_info() and
* match_expr_to_partition_keys().
*/
rel->nullable_partexprs = (List **) palloc0(sizeof(List *) * partnatts);
}
/*
* Return the PartitionBoundSpec associated to a partition if any, otherwise
* return NULL
*/
static PartitionBoundSpec *
hypo_get_boundspec(Oid tableid)
{
hypoTable *entry = hypo_find_table(tableid);
if (entry)
return entry->boundspec;
return NULL;
}
/*
* Return the Oid of the default partition if any, otherwise return InvalidOid
*/
static Oid
hypo_get_default_partition_oid(Oid parentid)
{
ListCell *lc;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (entry->parentid != parentid)
continue;
if (entry->boundspec->is_default)
return entry->oid;
}
return InvalidOid;
}
/*
* Deparse the stored PartitionBoundSpec data
*
* Heavily inspired on get_rule_expr()
*/
static char *
hypo_get_partbounddef(hypoTable *entry)
{
StringInfoData _buf;
StringInfo buf;
PartitionBoundSpec *spec = entry->boundspec;
ListCell *cell;
char *sep;
deparse_context _context;
deparse_context *context = &_context;
/* Simulate the context that should get passed */
initStringInfo(&_buf);
buf = &_buf;
_context.buf = &_buf;
if (spec->is_default)
{
appendStringInfoString(buf, "DEFAULT");
return buf->data;
}
switch (spec->strategy)
{
case PARTITION_STRATEGY_HASH:
Assert(spec->modulus > 0 && spec->remainder >= 0);
Assert(spec->modulus > spec->remainder);
appendStringInfoString(buf, "FOR VALUES");
appendStringInfo(buf, " WITH (modulus %d, remainder %d)",
spec->modulus, spec->remainder);
break;
case PARTITION_STRATEGY_LIST:
Assert(spec->listdatums != NIL);
appendStringInfoString(buf, "FOR VALUES IN (");
sep = "";
foreach(cell, spec->listdatums)
{
Const *val = castNode(Const, lfirst(cell));
appendStringInfoString(buf, sep);
get_const_expr(val, context, -1);
sep = ", ";
}
appendStringInfoChar(buf, ')');
break;
case PARTITION_STRATEGY_RANGE:
Assert(spec->lowerdatums != NIL &&
spec->upperdatums != NIL &&
list_length(spec->lowerdatums) ==
list_length(spec->upperdatums));
appendStringInfo(buf, "FOR VALUES FROM %s TO %s",
get_range_partbound_string(spec->lowerdatums),
get_range_partbound_string(spec->upperdatums));
break;
default:
elog(ERROR, "unrecognized partition strategy: %d",
(int) spec->strategy);
break;
}
return buf->data;
}
/*
* Deparse the stored PartitionKey data
*
* Heavily inspired on pg_get_partkeydef_worker()
*/
static char *
hypo_get_partkeydef(hypoTable *entry)
{
StringInfoData buf;
PartitionKey partkey = entry->partkey;
Oid relid;
ListCell *partexpr_item;
List *context;
int keyno;
char *str;
char *sep;
if (!partkey)
elog(ERROR, "hypopg: hypothetical table %s is not partitioned",
quote_identifier(entry->tablename));
relid = hypo_table_find_root_oid(entry);
partexpr_item = list_head(partkey->partexprs);
context = deparse_context_for(get_relation_name(relid), relid);
initStringInfo(&buf);
appendStringInfo(&buf, "PARTITION BY ");
switch(partkey->strategy)
{
case PARTITION_STRATEGY_HASH:
appendStringInfo(&buf, "HASH");
break;
case PARTITION_STRATEGY_LIST:
appendStringInfo(&buf, "LIST");
break;
case PARTITION_STRATEGY_RANGE:
appendStringInfo(&buf, "RANGE");
break;
default:
elog(ERROR, "hypopg: unexpected partition strategy %d",
(int) partkey->strategy);
}
appendStringInfoString(&buf, " (");
sep = "";
for (keyno = 0; keyno < partkey->partnatts; keyno++)
{
AttrNumber attnum = partkey->partattrs[keyno];
Oid keycoltype;
Oid keycolcollation;
Oid partcoll;
appendStringInfoString(&buf, sep);
sep = ", ";
if (attnum != 0)
{
/* Simple attribute reference */
char *attname;
int32 keycoltypmod;
attname = get_attname(relid, attnum, false);
appendStringInfoString(&buf, quote_identifier(attname));
get_atttypetypmodcoll(relid, attnum,
&keycoltype, &keycoltypmod,
&keycolcollation);
}
else
{
/* Expression */
Node *partkey;
if (partexpr_item == NULL)
elog(ERROR, "too few entries in partexprs list");
partkey = (Node *) lfirst(partexpr_item);
partexpr_item = lnext(partexpr_item);
/* Deparse */
str = deparse_expression(partkey, context, false, false);
/* Need parens if it's not a bare function call */
if (looks_like_function(partkey))
appendStringInfoString(&buf, str);
else
appendStringInfo(&buf, "(%s)", str);
keycoltype = exprType(partkey);
keycolcollation = exprCollation(partkey);
}
/* Add collation, if not default for column */
partcoll = partkey->partcollation[keyno];
if (OidIsValid(partcoll) && partcoll != keycolcollation)
appendStringInfo(&buf, " COLLATE %s",
generate_collation_name((partcoll)));
/* Add the operator class name, if not default */
get_opclass_name(entry->partopclass[keyno], keycoltype, &buf);
}
appendStringInfoChar(&buf, ')');
return buf.data;
}
/*
* palloc a new hypoTable, and give it a new OID, and some other global stuff.
*/
static hypoTable *
hypo_newTable(Oid parentid)
{
hypoTable *entry;
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(HypoMemoryContext);
entry = (hypoTable *) palloc0(sizeof(hypoTable));
entry->tablename = palloc0(NAMEDATALEN);
entry->boundspec = NULL; /* wil be generated later if needed */
entry->partkey = NULL; /* wil be generated later if needed */
MemoryContextSwitchTo(oldcontext);
/*
* If the given root table oid isn't present in hypoTables, we're
* partitioning it, so keep its oid, otherwise generate a new oid
*/
entry->parentid = hypo_table_find_parent_oid(parentid);
if (entry->parentid != InvalidOid)
entry->oid = hypo_getNewOid(parentid);
else
entry->oid = parentid;
return entry;
}
/*
* Find the direct parent oid of a hypothetical partition entry. Return NULL
* is it's a top level table.
*/
static Oid
hypo_table_find_parent_entry(hypoTable *entry)
{
return hypo_table_find_parent_oid(entry->parentid);
}
/*
* Find the direct parent oid of a hypothetical partition given it's parentid
* field. Return InvalidOid is it's a top level table.
*/
static Oid
hypo_table_find_parent_oid(Oid parentid)
{
ListCell *lc;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (entry->oid == parentid)
return entry->oid;
}
return InvalidOid;
}
/*
* Find the root table identifier of an hypothetical table
*/
static Oid
hypo_table_find_root_oid(hypoTable *entry)
{
Oid parent, last;
if (entry->parentid == InvalidOid)
return entry->oid;
parent = hypo_table_find_parent_entry(entry);
while (parent != InvalidOid)
{
last = parent;
parent = hypo_table_find_parent_entry(entry);
}
return last;
}
/*
* Return the stored hypothetical table corresponding to the Oid if any,
* otherwise return NULL
*/
static hypoTable *
hypo_find_table(Oid tableid)
{
ListCell *lc;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (entry->oid != tableid)
continue;
return entry;
}
return NULL;
}
/*
* Is the given name an hypothetical partition ?
*/
static bool
hypo_table_name_is_hypothetical(const char *name)
{
ListCell *lc;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (strcmp(entry->tablename, name) == 0)
return true;
}
return false;
}
/*
* Is the given relid an hypothetical partition ?
*/
bool
hypo_table_oid_is_hypothetical(Oid relid)
{
ListCell *lc;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (entry->oid == relid)
return true;
}
return false;
}
/* pfree all allocated memory for within an hypoTable and the entry itself. */
static void
hypo_table_pfree(hypoTable *entry)
{
/* pfree all memory that has been allocated */
pfree(entry->tablename);
if (entry->boundspec)
pfree(entry->boundspec);
if (entry->partkey)
{
pfree(entry->partkey->partopfamily);
pfree(entry->partkey->partopcintype);
pfree(entry->partkey->partsupfunc);
pfree(entry->partkey->partcollation);
pfree(entry->partkey->parttypid);
pfree(entry->partkey->parttypmod);
pfree(entry->partkey->parttyplen);
pfree(entry->partkey->parttypbyval);
pfree(entry->partkey->parttypalign);
pfree(entry->partkey->parttypcoll);
pfree(entry->partkey);
}
if (entry->partopclass)
pfree(entry->partopclass);
/* finally pfree the entry */
pfree(entry);
}
/*
* Remove an hypothetical table (or unpartition a previously hypothetically
* partitioned table) from the list of hypothetical tables. pfree (by calling
* hypo_table_pfree) all memory that has been allocated.
*/
static bool
hypo_table_remove(Oid tableid)
{
ListCell *lc;
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
if (entry->oid == tableid)
{
hypoTables = list_delete_ptr(hypoTables, entry);
hypo_table_pfree(entry);
return true;
}
}
return false;
}
/*
* Remove cleanly all hypothetical tables by calling hypo_table_remove() on
* each entry. hypo_table_remove() function pfree all allocated memory
*/
void
hypo_table_reset(void)
{
ListCell *lc;
/*
* The cell is removed in hypo_table_remove(), so we can't iterate using
* standard foreach / lnext macros.
*/
while ((lc = list_head(hypoTables)) != NULL)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
hypo_table_remove(entry->oid);
}
list_free(hypoTables);
hypoTables = NIL;
return;
}
/*
* Create an hypothetical partition from its CREATE TABLE parsetree. This
* function is where all the hypothetic partition creation is done, except the
* partition size estimation.
*/
static const hypoTable *
hypo_table_store_parsetree(CreateStmt *node, const char *queryString,
Oid parentid)
{
hypoTable *entry;
List *stmts;
CreateStmt *stmt = NULL;
ListCell *lc;
PartitionBoundSpec *boundspec;
/* Run parse analysis ... */
stmts = transformCreateStmt(node, queryString);
foreach(lc, stmts)
{
if (!IsA(lfirst(lc), CreateStmt))
continue;
stmt = (CreateStmt *) lfirst(lc);
/* There should only be one CreateStmt out of transformCreateStmt */
break;
}
Assert(stmt);
boundspec = stmt->partbound;
if (boundspec)
{
if (boundspec->is_default)
{
Oid defaultpart = hypo_get_default_partition_oid(parentid);
if (defaultpart != InvalidOid)
elog(ERROR, "partition \"%s\" conflicts with existing default partition \"%s\"",
quote_identifier(node->relation->relname),
quote_identifier(hypo_find_table(defaultpart)->tablename));
}
}
/* now create the hypothetical index entry */
entry = hypo_newTable(parentid);
strncpy(entry->tablename, node->relation->relname, NAMEDATALEN);
/* The CreateStmt specified a PARTITION BY clause, store it */
if (stmt->partspec)
hypo_generate_partkey(stmt, parentid, entry);
if (boundspec)
{
MemoryContext oldcontext;
ParseState *pstate;
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
oldcontext = MemoryContextSwitchTo(HypoMemoryContext);
entry->boundspec = hypo_transformPartitionBound(pstate,
hypo_find_table(parentid), boundspec);
MemoryContextSwitchTo(oldcontext);
}
hypo_addTable(entry);
return entry;
}
/*
* Adapted from parse_utilcmd.c / transformPartitionBound()
*/
static PartitionBoundSpec *
hypo_transformPartitionBound(ParseState *pstate, hypoTable *parent,
PartitionBoundSpec *spec)
{
PartitionBoundSpec *result_spec;
PartitionKey key = parent->partkey;
char strategy = get_partition_strategy(key);
int partnatts = get_partition_natts(key);
List *partexprs = get_partition_exprs(key);
/* Avoid scribbling on input */
result_spec = copyObject(spec);
if (spec->is_default)
{
if (strategy == PARTITION_STRATEGY_HASH)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("a hash-partitioned table may not have a default partition")));
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent's strategy to the default
* partition bound spec.
*/
result_spec->strategy = strategy;
return result_spec;
}
if (strategy == PARTITION_STRATEGY_HASH)
{
if (spec->strategy != PARTITION_STRATEGY_HASH)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid bound specification for a hash partition"),
parser_errposition(pstate, exprLocation((Node *) spec))));
if (spec->modulus <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("modulus for hash partition must be a positive integer")));
Assert(spec->remainder >= 0);
if (spec->remainder >= spec->modulus)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("remainder for hash partition must be less than modulus")));
}
else if (strategy == PARTITION_STRATEGY_LIST)
{
ListCell *cell;
char *colname;
Oid coltype;
int32 coltypmod;
if (spec->strategy != PARTITION_STRATEGY_LIST)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid bound specification for a list partition"),
parser_errposition(pstate, exprLocation((Node *) spec))));
/* Get the only column's name in case we need to output an error */
if (key->partattrs[0] != 0)
colname = get_attname(parent->oid,
key->partattrs[0], false);
else
colname = deparse_expression((Node *) linitial(partexprs),
deparse_context_for(parent->tablename,
parent->oid),
false, false);
/* Need its type data too */
coltype = get_partition_col_typid(key, 0);
coltypmod = get_partition_col_typmod(key, 0);
result_spec->listdatums = NIL;
foreach(cell, spec->listdatums)
{
A_Const *con = castNode(A_Const, lfirst(cell));
Const *value;
ListCell *cell2;
bool duplicate;
value = transformPartitionBoundValue(pstate, con,
colname, coltype, coltypmod);
/* Don't add to the result if the value is a duplicate */
duplicate = false;
foreach(cell2, result_spec->listdatums)
{
Const *value2 = castNode(Const, lfirst(cell2));
if (equal(value, value2))
{
duplicate = true;
break;
}
}
if (duplicate)
continue;
result_spec->listdatums = lappend(result_spec->listdatums,
value);
}
}
else if (strategy == PARTITION_STRATEGY_RANGE)
{
ListCell *cell1,
*cell2;
int i,
j;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid bound specification for a range partition"),
parser_errposition(pstate, exprLocation((Node *) spec))));
if (list_length(spec->lowerdatums) != partnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("FROM must specify exactly one value per partitioning column")));
if (list_length(spec->upperdatums) != partnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("TO must specify exactly one value per partitioning column")));
/*
* Once we see MINVALUE or MAXVALUE for one column, the remaining
* columns must be the same.
*/
validateInfiniteBounds(pstate, spec->lowerdatums);
validateInfiniteBounds(pstate, spec->upperdatums);
/* Transform all the constants */
i = j = 0;
result_spec->lowerdatums = result_spec->upperdatums = NIL;
forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
{
PartitionRangeDatum *ldatum = (PartitionRangeDatum *) lfirst(cell1);
PartitionRangeDatum *rdatum = (PartitionRangeDatum *) lfirst(cell2);
char *colname;
Oid coltype;
int32 coltypmod;
A_Const *con;
Const *value;
/* Get the column's name in case we need to output an error */
if (key->partattrs[i] != 0)
colname = get_attname(parent->oid,
key->partattrs[i], false);
else
{
colname = deparse_expression((Node *) list_nth(partexprs, j),
deparse_context_for(parent->tablename,
parent->oid),
false, false);
++j;
}
/* Need its type data too */
coltype = get_partition_col_typid(key, i);
coltypmod = get_partition_col_typmod(key, i);
if (ldatum->value)
{
con = castNode(A_Const, ldatum->value);
value = transformPartitionBoundValue(pstate, con,
colname,
coltype, coltypmod);
if (value->constisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot specify NULL in range bound")));
ldatum = copyObject(ldatum); /* don't scribble on input */
ldatum->value = (Node *) value;
}
if (rdatum->value)
{
con = castNode(A_Const, rdatum->value);
value = transformPartitionBoundValue(pstate, con,
colname,
coltype, coltypmod);
if (value->constisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot specify NULL in range bound")));
rdatum = copyObject(rdatum); /* don't scribble on input */
rdatum->value = (Node *) value;
}
result_spec->lowerdatums = lappend(result_spec->lowerdatums,
ldatum);
result_spec->upperdatums = lappend(result_spec->upperdatums,
rdatum);
++i;
}
}
else
elog(ERROR, "unexpected partition strategy: %d", (int) strategy);
return result_spec;
}
/*
* If this rel is the table we want to partition hypothetically, we inject
* the hypothetical partitioning to this rel
*/
void
hypo_injectHypotheticalPartitioning(PlannerInfo *root,
Oid relationObjectId,
RelOptInfo *rel)
{
hypoTable *parent;
List *inhoids;
int nparts;
Assert(HYPO_ENABLED());
Assert(hypo_table_oid_is_hypothetical(relationObjectId));
parent = hypo_find_table(relationObjectId);
/* get all of the partition oids */
inhoids = hypo_find_inheritance_children(parent);
nparts = list_length(inhoids);
/*
* if this rel is parent, prepare some structures to inject
* hypothetical partitioning
*/
if(!HYPO_RTI_IS_TAGGED(rel->relid,root))
{
int i;
int oldsize = root->simple_rel_array_size;
RangeTblEntry *rte;
ListCell *cell;
AppendRelInfo *appinfo;
/* resize and clean rte and rel arrays */
root->simple_rel_array_size = oldsize + nparts + 1;
root->simple_rel_array = (RelOptInfo **)
repalloc(root->simple_rel_array,
root->simple_rel_array_size *
sizeof(RelOptInfo *));
root->simple_rte_array = (RangeTblEntry **)
repalloc(root->simple_rte_array,
root->simple_rel_array_size *
sizeof(RangeTblEntry *));
for (i=oldsize; i<root->simple_rel_array_size; i++)
{
root->simple_rel_array[i] = NULL;
root->simple_rte_array[i] = NULL;
}
/* rewrite and restore this rel's rte */
rte = root->simple_rte_array[rel->relid];
rte->relkind = RELKIND_PARTITIONED_TABLE;
rte->inh = true;
root->simple_rte_array[rel->relid] = rte;
root->simple_rte_array[oldsize] = rte;
root->parse->rtable = lappend(root->parse->rtable,
root->simple_rte_array[oldsize]);
HYPO_TAG_RTI(rel->relid, root);
HYPO_TAG_RTI(oldsize, root);
/*
* create RangeTblEntries and AppendRelInfos hypothetically
* for all hypothetical partitions
*/
i = 1;
foreach(cell, inhoids)
{
int newrelid;
Oid childOid = lfirst_oid(cell);
hypoTable *child;
RangeTblEntry *childrte;
Relation parentrel;
child = hypo_find_table(childOid);
newrelid = oldsize + i;
childrte = copyObject(rte);
childrte->rtekind = RTE_RELATION;
childrte->relid = relationObjectId; //originalOID;
childrte->relkind = RELKIND_RELATION;
childrte->inh = false;
if(!childrte->alias)
childrte->alias = makeNode(Alias);
childrte->alias->aliasname = child->tablename;
/* FIXME maybe use a mapping array here instead of rte->values_lists*/
childrte->values_lists = list_make1_oid(child->oid); //partitionOID
root->simple_rte_array[newrelid] = childrte;
HYPO_TAG_RTI(newrelid, root);
appinfo = makeNode(AppendRelInfo);
appinfo->parent_relid = rel->relid;
appinfo->child_relid = newrelid;
parentrel = heap_open(relationObjectId, NoLock);
appinfo->parent_reltype = parentrel->rd_rel->reltype;
appinfo->child_reltype = parentrel->rd_rel->reltype;
make_inh_translation_list(parentrel, parentrel, newrelid,
&appinfo->translated_vars);
heap_close(parentrel, NoLock);
appinfo->parent_reloid = relationObjectId;
root->append_rel_list = lappend(root->append_rel_list,
appinfo);
root->parse->rtable = lappend(root->parse->rtable,
root->simple_rte_array[newrelid]);
i++;
}
/* add partition info to this rel */
hypo_partition_table(root, rel, parent);
}
/*
* If this rel is partitioned by hash, we should rewrite the rel->pages
* and the rel->pages here according to the number of partitions.
*
* If this rel is partitioned list or range, we add the partition constraints
* to the rte->securityQuals so that the rel->rows is computed correctly at
* the set_baserel_size_estimates(). We shouldn't rewrite the rel->pages
* and the rel->tuples here, because they will be rewritten at the later hook.
*/
if (rel->reloptkind != RELOPT_BASEREL
&&HYPO_RTI_IS_TAGGED(rel->relid,root))
{
if (parent->partkey->strategy == PARTITION_STRATEGY_HASH)
{
double pages;
pages = ceil(rel->pages / nparts);
rel->pages = (BlockNumber)pages;
rel->tuples = clamp_row_est(rel->tuples / nparts);
}
else
{
List *constraints;
/* get its partition constraints */
constraints = hypo_get_partition_constraints(root, rel, parent);
/*
* to compute rel->rows at set_baserel_size_estimates using parent's
* statistics, parent's tuples and baserestrictinfo, we add the partition
* constraints to its rte->securityQuals
*/
planner_rt_fetch(rel->relid, root)->securityQuals = list_make1(constraints);
}
}
}
/*
* If this rel is partition, we remove the partition constraints from the
* its rel->baserestrictinfo and rewrite some items of its RelOptInfo:
* the rel->pages, the rel->tuples rel->baserestrictcost. After that
* we call the set_plain_rel_pathlist() to re-create its pathlist using
* the new RelOptInfo.
*
*/
void hypo_setPartitionPathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte)
{
ListCell *l;
Index parentRTindex;
RelOptInfo *parentrel;
hypoTable *parent = hypo_find_table(rte->relid);
List *constraints = hypo_get_partition_constraints(root, rel, parent);
PlannerInfo *root_dummy;
Selectivity selectivity;
double pages;
/* If this rel is partitioned by hash, nothing to do */
if (parent->partkey->strategy == PARTITION_STRATEGY_HASH)
return;
/*
* get the parent's rel and copy its rel->baserestrictinfo to
* the own rel->baserestrictinfo.
* this part is inspired on set_append_rel_size().
*/
foreach(l, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *)lfirst(l);
List *childquals = NIL;
Index cq_min_security = UINT_MAX;
ListCell *lc;
if(appinfo->child_relid == rti)
{
parentRTindex = appinfo->parent_relid;
parentrel = root->simple_rel_array[parentRTindex];
foreach(lc, parentrel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
Node *childqual;
ListCell *lc2;
Assert(IsA(rinfo, RestrictInfo));
childqual = adjust_appendrel_attrs(root,
(Node *) rinfo->clause,
1, &appinfo);
childqual = eval_const_expressions(root, childqual);
/* might have gotten an AND clause, if so flatten it */
foreach(lc2, make_ands_implicit((Expr *) childqual))
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
!contain_vars_of_level(onecq, 0) &&
!contain_volatile_functions(onecq);
if (pseudoconstant)
{
/* tell createplan.c to check for gating quals */
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
childquals = lappend(childquals,
make_restrictinfo((Expr *) onecq,
rinfo->is_pushed_down,
rinfo->outerjoin_delayed,
pseudoconstant,
rinfo->security_level,
NULL, NULL, NULL));
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
}
rel->baserestrictinfo = childquals;
rel->baserestrict_min_security = cq_min_security;
break;
}
}
/*
* make dummy PlannerInfo to compute the selectivity, and then rewrite
* tuples and pages using this selectivity
*/
root_dummy = makeNode(PlannerInfo);
root_dummy = root;
root_dummy->simple_rel_array[rti] = rel;
selectivity = clauselist_selectivity(root_dummy,
constraints,
0,
JOIN_INNER,
NULL);
pages = ceil(rel->pages * selectivity);
rel->pages = (BlockNumber)pages;
rel->tuples = clamp_row_est(rel->tuples * selectivity);
/* recompute the rel->baserestrictcost*/
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
/*
* call the set_plain_rel_pathlist() to re-create its pathlist using
* the new RelOptInfo
*/
set_plain_rel_pathlist(root, rel, rte);
}
/*
* If this is the table we want to hypothetically partition, modifies its
* metadata to add partitioning information
*/
static void
hypo_partition_table(PlannerInfo *root, RelOptInfo *rel, hypoTable *entry)
{
PartitionDesc partdesc;
PartitionKey partkey;
partdesc = hypo_generate_partitiondesc(entry);
partkey = entry->partkey;
rel->part_scheme = hypo_find_partition_scheme(root, partkey);
rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
rel->nparts = partdesc->nparts;
hypo_generate_partition_key_exprs(entry, rel);
rel->partition_qual = NIL; //FIX ME for multi-level partition
}
/*
* Returns a List of partition constraints from its partition bound spec
*
* It is inspired on get_relation_constraints()
*/
static List *
hypo_get_partition_constraints(PlannerInfo *root, RelOptInfo *rel, hypoTable *parent)
{
ListCell *lc;
Oid childOid;
hypoTable *child;
PartitionBoundSpec *spec;
List *constraints;
/* FIXME maybe use a mapping array here instead of rte->values_lists*/
lc = list_head(planner_rt_fetch(rel->relid, root)->values_lists);
childOid = lfirst_oid(lc);
child = hypo_find_table(childOid);
spec = child->boundspec;
/* get its partition constraints */
constraints = hypo_get_qual_from_partbound(parent,spec);
if (constraints)
{
/*
* Run each expression through const-simplification and
* canonicalization similar to check constraints.
*/
constraints = (List *) eval_const_expressions(root, (Node *) constraints);
/* FIXME this func will be modified at pg11 */
// constraints = (List *) canonicalize_qual((Expr *) constraints, true);
/* Fix Vars to have the desired varno */
if (rel->relid != 1)
ChangeVarNodes((Node *) constraints, 1, rel->relid, 0);
}
return constraints;
}
/*
* Return the list of executable expressions as partition constraint
*
* Heavily inspired on get_qual_from_partbound
*/
static List *
hypo_get_qual_from_partbound(hypoTable *parent, PartitionBoundSpec *spec)
{
PartitionKey key = parent->partkey;
List *my_qual = NIL;
Assert(key != NULL);
switch (key->strategy)
{
case PARTITION_STRATEGY_HASH:
Assert(spec->strategy == PARTITION_STRATEGY_HASH);
my_qual = hypo_get_qual_for_hash(parent, spec);
break;
case PARTITION_STRATEGY_LIST:
Assert(spec->strategy == PARTITION_STRATEGY_LIST);
my_qual = hypo_get_qual_for_list(parent, spec);
break;
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
my_qual = hypo_get_qual_for_range(parent, spec, false);
break;
default:
elog(ERROR, "unexpected partition strategy: %d",
(int) key->strategy);
}
return my_qual;
}
/*
* Returns a CHECK constraint expression to use as a hash partition's
* constraint, given the parent entry and partition bound structure.
*
* Heavily inspired on get_qual_for_hash
*/
static List *
hypo_get_qual_for_hash(hypoTable *parent, PartitionBoundSpec *spec)
{
PartitionKey key = parent->partkey;
FuncExpr *fexpr;
Node *relidConst;
Node *modulusConst;
Node *remainderConst;
List *args;
ListCell *partexprs_item;
int i;
/* Fixed arguments. */
relidConst = (Node *) makeConst(OIDOID,
-1,
InvalidOid,
sizeof(Oid),
ObjectIdGetDatum(parent->oid), //parentid?
false,
true);
modulusConst = (Node *) makeConst(INT4OID,
-1,
InvalidOid,
sizeof(int32),
Int32GetDatum(spec->modulus),
false,
true);
remainderConst = (Node *) makeConst(INT4OID,
-1,
InvalidOid,
sizeof(int32),
Int32GetDatum(spec->remainder),
false,
true);
args = list_make3(relidConst, modulusConst, remainderConst);
partexprs_item = list_head(key->partexprs);
/* Add an argument for each key column. */
for (i = 0; i < key->partnatts; i++)
{
Node *keyCol;
/* Left operand */
if (key->partattrs[i] != 0)
{
keyCol = (Node *) makeVar(1,
key->partattrs[i],
key->parttypid[i],
key->parttypmod[i],
key->parttypcoll[i],
0);
}
else
{
keyCol = (Node *) copyObject(lfirst(partexprs_item));
partexprs_item = lnext(partexprs_item);
}
args = lappend(args, keyCol);
}
fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
BOOLOID,
args,
InvalidOid,
InvalidOid,
COERCE_EXPLICIT_CALL);
return list_make1(fexpr);
}
/*
* Returns an implicit-AND list of expressions to use as a list partition's
* constraint, given the parent entry and partition bound structure.
*
* Heavily inspired on get_qual_for_list
*/
static List *
hypo_get_qual_for_list(hypoTable *parent, PartitionBoundSpec *spec)
{
PartitionKey key = parent->partkey;
List *result;
Expr *keyCol;
Expr *opexpr;
NullTest *nulltest;
ListCell *cell;
List *elems = NIL;
bool list_has_null = false;
/*
* Only single-column list partitioning is supported, so we are worried
* only about the partition key with index 0.
*/
Assert(key->partnatts == 1);
/* Construct Var or expression representing the partition column */
if (key->partattrs[0] != 0)
keyCol = (Expr *) makeVar(1,
key->partattrs[0],
key->parttypid[0],
key->parttypmod[0],
key->parttypcoll[0],
0);
else
keyCol = (Expr *) copyObject(linitial(key->partexprs));
/*
* For default list partition, collect datums for all the partitions. The
* default partition constraint should check that the partition key is
* equal to none of those.
*/
if (spec->is_default)
{
int i;
int ndatums = 0;
PartitionDesc pdesc = hypo_generate_partitiondesc(parent);
PartitionBoundInfo boundinfo = pdesc->boundinfo;
if (boundinfo)
{
ndatums = boundinfo->ndatums;
if (partition_bound_accepts_nulls(boundinfo))
list_has_null = true;
}
/*
* If default is the only partition, there need not be any partition
* constraint on it.
*/
if (ndatums == 0 && !list_has_null)
return NIL;
for (i = 0; i < ndatums; i++)
{
Const *val;
/*
* Construct Const from known-not-null datum. We must be careful
* to copy the value, because our result has to be able to outlive
* the relcache entry we're copying from.
*/
val = makeConst(key->parttypid[0],
key->parttypmod[0],
key->parttypcoll[0],
key->parttyplen[0],
datumCopy(*boundinfo->datums[i],
key->parttypbyval[0],
key->parttyplen[0]),
false, /* isnull */
key->parttypbyval[0]);
elems = lappend(elems, val);
}
}
else
{
/*
* Create list of Consts for the allowed values, excluding any nulls.
*/
foreach(cell, spec->listdatums)
{
Const *val = castNode(Const, lfirst(cell));
if (val->constisnull)
list_has_null = true;
else
elems = lappend(elems, copyObject(val));
}
}
if (elems)
{
/*
* Generate the operator expression from the non-null partition
* values.
*/
opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
keyCol, (Expr *) elems);
}
else
{
/*
* If there are no partition values, we don't need an operator
* expression.
*/
opexpr = NULL;
}
if (!list_has_null)
{
/*
* Gin up a "col IS NOT NULL" test that will be AND'd with the main
* expression. This might seem redundant, but the partition routing
* machinery needs it.
*/
nulltest = makeNode(NullTest);
nulltest->arg = keyCol;
nulltest->nulltesttype = IS_NOT_NULL;
nulltest->argisrow = false;
nulltest->location = -1;
result = opexpr ? list_make2(nulltest, opexpr) : list_make1(nulltest);
}
else
{
/*
* Gin up a "col IS NULL" test that will be OR'd with the main
* expression.
*/
nulltest = makeNode(NullTest);
nulltest->arg = keyCol;
nulltest->nulltesttype = IS_NULL;
nulltest->argisrow = false;
nulltest->location = -1;
if (opexpr)
{
Expr *or;
or = makeBoolExpr(OR_EXPR, list_make2(nulltest, opexpr), -1);
result = list_make1(or);
}
else
result = list_make1(nulltest);
}
/*
* Note that, in general, applying NOT to a constraint expression doesn't
* necessarily invert the set of rows it accepts, because NOT (NULL) is
* NULL. However, the partition constraints we construct here never
* evaluate to NULL, so applying NOT works as intended.
*/
if (spec->is_default)
{
result = list_make1(make_ands_explicit(result));
result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
return result;
}
/*
* Returns an implicit-AND list of expressions to use as a range partition's
* constraint, given the parent entry and partition bound structure.
*
* Heavily inspired on get_qual_for_range
*/
static List *
hypo_get_qual_for_range(hypoTable *parent, PartitionBoundSpec *spec, bool for_default)
{
List *result = NIL;
ListCell *cell1,
*cell2,
*partexprs_item,
*partexprs_item_saved;
int i,
j;
PartitionRangeDatum *ldatum,
*udatum;
PartitionKey key = parent->partkey;
Expr *keyCol;
Const *lower_val,
*upper_val;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
current_or_arm;
ListCell *lower_or_start_datum,
*upper_or_start_datum;
bool need_next_lower_arm,
need_next_upper_arm;
if (spec->is_default)
{
List *or_expr_args = NIL;
PartitionDesc pdesc = hypo_generate_partitiondesc(parent);
Oid *inhoids = pdesc->oids;
int nparts = pdesc->nparts,
i;
for (i = 0; i < nparts; i++)
{
Oid inhrelid = inhoids[i]; //is this a parent oid or dummy child oid?
HeapTuple tuple;
Datum datum;
bool isnull;
PartitionBoundSpec *bspec;
elog(NOTICE,"inhrelid : %u",inhrelid);
tuple = SearchSysCache1(RELOID, inhrelid);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", inhrelid);
datum = SysCacheGetAttr(RELOID, tuple,
Anum_pg_class_relpartbound,
&isnull);
Assert(!isnull);
bspec = (PartitionBoundSpec *)
stringToNode(TextDatumGetCString(datum));
if (!IsA(bspec, PartitionBoundSpec))
elog(ERROR, "expected PartitionBoundSpec");
if (!bspec->is_default)
{
List *part_qual;
part_qual = hypo_get_qual_for_range(parent, bspec, true);
/*
* AND the constraints of the partition and add to
* or_expr_args
*/
or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
? makeBoolExpr(AND_EXPR, part_qual, -1)
: linitial(part_qual));
}
ReleaseSysCache(tuple);
}
if (or_expr_args != NIL)
{
Expr *other_parts_constr;
/*
* Combine the constraints obtained for non-default partitions
* using OR. As requested, each of the OR's args doesn't include
* the NOT NULL test for partition keys (which is to avoid its
* useless repetition). Add the same now.
*/
other_parts_constr =
makeBoolExpr(AND_EXPR,
lappend(get_range_nulltest(key),
list_length(or_expr_args) > 1
? makeBoolExpr(OR_EXPR, or_expr_args,
-1)
: linitial(or_expr_args)),
-1);
/*
* Finally, the default partition contains everything *NOT*
* contained in the non-default partitions.
*/
result = list_make1(makeBoolExpr(NOT_EXPR,
list_make1(other_parts_constr), -1));
}
return result;
}
lower_or_start_datum = list_head(spec->lowerdatums);
upper_or_start_datum = list_head(spec->upperdatums);
num_or_arms = key->partnatts;
/*
* If it is the recursive call for default, we skip the get_range_nulltest
* to avoid accumulating the NullTest on the same keys for each partition.
*/
if (!for_default)
result = get_range_nulltest(key);
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
* column's type. If equal, we emit single keyCol = common_value
* expression. Starting from the first column for which the corresponding
* lower and upper bound datums are not equal, we generate OR expressions
* as shown in the function's header comment.
*/
i = 0;
partexprs_item = list_head(key->partexprs);
partexprs_item_saved = partexprs_item; /* placate compiler */
forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
{
EState *estate;
MemoryContext oldcxt;
Expr *test_expr;
ExprState *test_exprstate;
Datum test_result;
bool isNull;
ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
udatum = castNode(PartitionRangeDatum, lfirst(cell2));
/*
* Since get_range_key_properties() modifies partexprs_item, and we
* might need to start over from the previous expression in the later
* part of this function, save away the current value.
*/
partexprs_item_saved = partexprs_item;
get_range_key_properties(key, i, ldatum, udatum,
&partexprs_item,
&keyCol,
&lower_val, &upper_val);
/*
* If either value is NULL, the corresponding partition bound is
* either MINVALUE or MAXVALUE, and we treat them as unequal, because
* even if they're the same, there is no common value to equate the
* key column with.
*/
if (!lower_val || !upper_val)
break;
/* Create the test expression */
estate = CreateExecutorState();
oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
(Expr *) lower_val,
(Expr *) upper_val);
fix_opfuncids((Node *) test_expr);
test_exprstate = ExecInitExpr(test_expr, NULL);
test_result = ExecEvalExprSwitchContext(test_exprstate,
GetPerTupleExprContext(estate),
&isNull);
MemoryContextSwitchTo(oldcxt);
FreeExecutorState(estate);
/* If not equal, go generate the OR expressions */
if (!DatumGetBool(test_result))
break;
/*
* The bounds for the last key column can't be equal, because such a
* range partition would never be allowed to be defined (it would have
* an empty range otherwise).
*/
if (i == key->partnatts - 1)
elog(ERROR, "invalid range bound specification");
/* Equal, so generate keyCol = lower_val expression */
result = lappend(result,
make_partition_op_expr(key, i, BTEqualStrategyNumber,
keyCol, (Expr *) lower_val));
i++;
}
/* First pair of lower_val and upper_val that are not equal. */
lower_or_start_datum = cell1;
upper_or_start_datum = cell2;
/* OR will have as many arms as there are key columns left. */
num_or_arms = key->partnatts - i;
current_or_arm = 0;
lower_or_arms = upper_or_arms = NIL;
need_next_lower_arm = need_next_upper_arm = true;
while (current_or_arm < num_or_arms)
{
List *lower_or_arm_args = NIL,
*upper_or_arm_args = NIL;
/* Restart scan of columns from the i'th one */
j = i;
partexprs_item = partexprs_item_saved;
for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum)
{
PartitionRangeDatum *ldatum_next = NULL,
*udatum_next = NULL;
ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
if (lnext(cell1))
ldatum_next = castNode(PartitionRangeDatum,
lfirst(lnext(cell1)));
udatum = castNode(PartitionRangeDatum, lfirst(cell2));
if (lnext(cell2))
udatum_next = castNode(PartitionRangeDatum,
lfirst(lnext(cell2)));
get_range_key_properties(key, j, ldatum, udatum,
&partexprs_item,
&keyCol,
&lower_val, &upper_val);
if (need_next_lower_arm && lower_val)
{
uint16 strategy;
/*
* For the non-last columns of this arm, use the EQ operator.
* For the last column of this arm, use GT, unless this is the
* last column of the whole bound check, or the next bound
* datum is MINVALUE, in which case use GE.
*/
if (j - i < current_or_arm)
strategy = BTEqualStrategyNumber;
else if (j == key->partnatts - 1 ||
(ldatum_next &&
ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
strategy = BTGreaterEqualStrategyNumber;
else
strategy = BTGreaterStrategyNumber;
lower_or_arm_args = lappend(lower_or_arm_args,
make_partition_op_expr(key, j,
strategy,
keyCol,
(Expr *) lower_val));
}
if (need_next_upper_arm && upper_val)
{
uint16 strategy;
/*
* For the non-last columns of this arm, use the EQ operator.
* For the last column of this arm, use LT, unless the next
* bound datum is MAXVALUE, in which case use LE.
*/
if (j - i < current_or_arm)
strategy = BTEqualStrategyNumber;
else if (udatum_next &&
udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
strategy = BTLessEqualStrategyNumber;
else
strategy = BTLessStrategyNumber;
upper_or_arm_args = lappend(upper_or_arm_args,
make_partition_op_expr(key, j,
strategy,
keyCol,
(Expr *) upper_val));
}
/*
* Did we generate enough of OR's arguments? First arm considers
* the first of the remaining columns, second arm considers first
* two of the remaining columns, and so on.
*/
++j;
if (j - i > current_or_arm)
{
/*
* We must not emit any more arms if the new column that will
* be considered is unbounded, or this one was.
*/
if (!lower_val || !ldatum_next ||
ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
need_next_lower_arm = false;
if (!upper_val || !udatum_next ||
udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
need_next_upper_arm = false;
break;
}
}
if (lower_or_arm_args != NIL)
lower_or_arms = lappend(lower_or_arms,
list_length(lower_or_arm_args) > 1
? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1)
: linitial(lower_or_arm_args));
if (upper_or_arm_args != NIL)
upper_or_arms = lappend(upper_or_arms,
list_length(upper_or_arm_args) > 1
? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1)
: linitial(upper_or_arm_args));
/* If no work to do in the next iteration, break away. */
if (!need_next_lower_arm && !need_next_upper_arm)
break;
++current_or_arm;
}
/*
* Generate the OR expressions for each of lower and upper bounds (if
* required), and append to the list of implicitly ANDed list of
* expressions.
*/
if (lower_or_arms != NIL)
result = lappend(result,
list_length(lower_or_arms) > 1
? makeBoolExpr(OR_EXPR, lower_or_arms, -1)
: linitial(lower_or_arms));
if (upper_or_arms != NIL)
result = lappend(result,
list_length(upper_or_arms) > 1
? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
: linitial(upper_or_arms));
/*
* As noted above, for non-default, we return list with constant TRUE. If
* the result is NIL during the recursive call for default, it implies
* this is the only other partition which can hold every value of the key
* except NULL. Hence we return the NullTest result skipped earlier.
*/
if (result == NIL)
result = for_default
? get_range_nulltest(key)
: list_make1(makeBoolConst(true, false));
return result;
}
#endif
/*
* SQL wrapper to create an hypothetical partition with his parsetree
*/
Datum
hypopg_add_partition(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM < 100000
HYPO_PARTITION_NOT_SUPPORTED();
#else
const char *partname = PG_GETARG_NAME(0)->data;
char *partitionof = TextDatumGetCString(PG_GETARG_TEXT_PP(1));
StringInfoData sql;
Oid parentid;
const hypoTable *entry;
List *parsetree_list;
RawStmt *raw_stmt;
CreateStmt *stmt;
RangeVar *rv;
TupleDesc tupdesc;
Datum values[HYPO_ADD_PART_COLS];
bool nulls[HYPO_ADD_PART_COLS];
int i = 0;
if (!PG_ARGISNULL(2))
{
elog(ERROR, "hypopg: multi-level partitioning is not supported yet");
}
if (hypo_table_name_is_hypothetical(partname))
elog(ERROR, "hypopg: hypothetical table %s already exists",
quote_identifier(partname));
if (RelnameGetRelid(partname) != InvalidOid)
elog(ERROR, "hypopg: real table %s already exists",
quote_identifier(partname));
tupdesc = CreateTemplateTupleDesc(HYPO_ADD_PART_COLS, false);
TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "relid", OIDOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) ++i, "tablename", TEXTOID, -1, 0);
Assert(i == HYPO_ADD_PART_COLS);
tupdesc = BlessTupleDesc(tupdesc);
MemSet(nulls, 0, sizeof(nulls));
i = 0;
initStringInfo(&sql);
appendStringInfo(&sql, "CREATE TABLE %s %s",
quote_identifier(partname), partitionof);
parsetree_list = pg_parse_query(sql.data);
raw_stmt = (RawStmt *) linitial(parsetree_list);
stmt = (CreateStmt *) raw_stmt->stmt;
Assert(IsA(stmt, CreateStmt));
if (!stmt->partbound || !stmt->inhRelations)
elog(ERROR, "hypopg: you must specify a PARTITION OF clause");
if (stmt->partspec)
elog(ERROR, "hypopg: multi-level partitioning is not supported yet");
/* Find the parent's oid */
if (list_length(stmt->inhRelations) != 1)
elog(ERROR, "hypopg: unexpected list lenght %d, expected 1",
list_length(stmt->inhRelations));
rv = (RangeVar *) linitial(stmt->inhRelations);
/* FIXME change this when handling multi-level partitioning */
parentid = RangeVarGetRelid(rv, AccessShareLock, false);
if (!hypo_table_oid_is_hypothetical(parentid))
elog(ERROR, "hypopg: %s must be hypothetically partitioned first",
quote_identifier(rv->relname));
entry = hypo_table_store_parsetree((CreateStmt *) stmt, sql.data,
parentid);
pfree(sql.data);
values[i++] = ObjectIdGetDatum(entry->oid);
values[i++] = CStringGetTextDatum(entry->tablename);
Assert(i == HYPO_ADD_PART_COLS);
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
#endif
}
/*
* SQL wrapper to drop an hypothetical partition, or unpartition a previsously
* hypothatically partitioned existing table.
*/
Datum
hypopg_drop_table(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM < 100000
HYPO_PARTITION_NOT_SUPPORTED();
#else
Oid tableid = PG_GETARG_OID(0);
PG_RETURN_BOOL(hypo_table_remove(tableid));
#endif
}
/*
* SQL wrapper to hypothetically partition an existing table.
*/
Datum
hypopg_partition_table(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM < 100000
HYPO_PARTITION_NOT_SUPPORTED();
#else
Oid tableid = PG_GETARG_OID(0);
char *partition_by = TextDatumGetCString(PG_GETARG_TEXT_PP(1));
const hypoTable *entry;
char *root_name = NULL;
char *nspname = NULL;
bool found = false;
HeapTuple tp;
Relation relation;
List *children = NIL;
List *parsetree_list;
ListCell *lc;
StringInfoData sql;
RawStmt *raw_stmt;
tp = SearchSysCache1(RELOID, ObjectIdGetDatum(tableid));
if (HeapTupleIsValid(tp))
{
Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp);
if (reltup->relkind == RELKIND_RELATION)
found = true;
root_name = pstrdup(NameStr(reltup->relname));
nspname = get_namespace_name(reltup->relnamespace);
ReleaseSysCache(tp);
}
else
elog(ERROR, "hypopg: Object %d does not exists", tableid);
if (!found)
elog(ERROR, "hypopg: %s.%s is not a simple table",
quote_identifier(nspname), quote_identifier(root_name));
relation = heap_open(tableid, AccessShareLock);
children = find_inheritance_children(tableid, AccessShareLock);
heap_close(relation, AccessShareLock);
if (children != NIL)
elog(ERROR, "hypopg: Table %s.%s has inherited tables",
quote_identifier(nspname), quote_identifier(root_name));
foreach(lc, hypoTables)
{
hypoTable *search = (hypoTable *) lfirst(lc);
if (search->oid == tableid)
elog(ERROR, "hypopg: Table %s.%s is already hypothetically partitioned",
quote_identifier(nspname), quote_identifier(root_name));
}
initStringInfo(&sql);
appendStringInfo(&sql, "CREATE TABLE hypoTable (LIKE %s.%s) %s",
quote_identifier(nspname), quote_identifier(root_name), partition_by);
parsetree_list = pg_parse_query(sql.data);
raw_stmt = (RawStmt *) linitial(parsetree_list);
Assert(IsA(raw_stmt->stmt, CreateStmt));
entry = hypo_table_store_parsetree((CreateStmt *) raw_stmt->stmt, sql.data,
tableid);
/* special case for root table, copy it's original name */
strncpy(entry->tablename, root_name, NAMEDATALEN);
pfree(sql.data);
pfree(root_name);
pfree(nspname);
PG_RETURN_BOOL(entry != NULL);
#endif
}
/*
* SQL wrapper to remove all declared hypothetical partitions.
*/
Datum
hypopg_reset_table(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM < 100000
HYPO_PARTITION_NOT_SUPPORTED();
#else
hypo_table_reset();
PG_RETURN_VOID();
#endif
}
/*
* List created hypothetical tables
*/
Datum
hypopg_table(PG_FUNCTION_ARGS)
{
#if PG_VERSION_NUM < 100000
HYPO_PARTITION_NOT_SUPPORTED();
#else
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
MemoryContext per_query_ctx;
MemoryContext oldcontext;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
ListCell *lc;
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsinfo->allowedModes & SFRM_Materialize))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialize mode required, but it is not " \
"allowed in this context")));
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
oldcontext = MemoryContextSwitchTo(per_query_ctx);
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
tupstore = tuplestore_begin_heap(true, false, work_mem);
rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
MemoryContextSwitchTo(oldcontext);
foreach(lc, hypoTables)
{
hypoTable *entry = (hypoTable *) lfirst(lc);
Datum values[HYPO_TABLE_NB_COLS];
bool nulls[HYPO_TABLE_NB_COLS];
int i = 0;
memset(values, 0, sizeof(values));
memset(nulls, 0, sizeof(nulls));
values[i++] = ObjectIdGetDatum(entry->oid);
values[i++] = CStringGetTextDatum(entry->tablename);
if (entry->parentid == InvalidOid)
nulls[i++] = true;
else
values[i++] = ObjectIdGetDatum(entry->parentid);
if (entry->partkey)
values[i++] = CStringGetTextDatum(hypo_get_partkeydef(entry));
else
nulls[i++] = true;
if (entry->boundspec)
values[i++] = CStringGetTextDatum(hypo_get_partbounddef(entry));
else
nulls[i++] = true;
Assert(i == HYPO_TABLE_NB_COLS);
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
/* clean up and return the tuplestore */
tuplestore_donestoring(tupstore);
return (Datum) 0;
#endif
}