mirror of
https://github.com/HypoPG/hypopg
synced 2026-05-24 09:38:21 +00:00
Add infrastructure to declare hypothetical partitioning.
This commit is contained in:
parent
74a6fa8fd4
commit
c44e6ff773
6 changed files with 3014 additions and 5 deletions
2
Makefile
2
Makefile
|
|
@ -7,7 +7,7 @@ REGRESS_OPTS = --inputdir=test
|
|||
PG_CONFIG = pg_config
|
||||
|
||||
MODULE_big = hypopg
|
||||
OBJS = hypopg.o hypopg_import.o hypopg_index.o
|
||||
OBJS = hypopg.o hypopg_import.o hypopg_index.o hypopg_table.o
|
||||
|
||||
all:
|
||||
|
||||
|
|
|
|||
|
|
@ -8,16 +8,22 @@
|
|||
|
||||
SET client_encoding = 'UTF8';
|
||||
|
||||
CREATE FUNCTION hypopg_reset_index()
|
||||
RETURNS void
|
||||
LANGUAGE C VOLATILE COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_reset_index';
|
||||
-- General functions
|
||||
--
|
||||
|
||||
CREATE FUNCTION hypopg_reset()
|
||||
RETURNS void
|
||||
LANGUAGE C VOLATILE COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_reset';
|
||||
|
||||
-- Hypothetical indexes related functions
|
||||
--
|
||||
|
||||
CREATE FUNCTION hypopg_reset_index()
|
||||
RETURNS void
|
||||
LANGUAGE C VOLATILE COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_reset_index';
|
||||
|
||||
CREATE FUNCTION
|
||||
hypopg_create_index(IN sql_order text, OUT indexrelid oid, OUT indexname text)
|
||||
RETURNS SETOF record
|
||||
|
|
@ -63,3 +69,32 @@ hypopg_get_indexdef(IN indexid oid)
|
|||
RETURNS text
|
||||
LANGUAGE C STRICT VOLATILE COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_get_indexdef';
|
||||
|
||||
-- Hypothetical partitioning related functions
|
||||
--
|
||||
|
||||
CREATE FUNCTION
|
||||
hypopg_add_partition(IN partition_name name, IN partition_of_clause text,
|
||||
IN partition_by_clause text DEFAULT NULL,
|
||||
OUT relid oid, OUT tablename text)
|
||||
RETURNS SETOF record
|
||||
LANGUAGE C VOLATILE COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_add_partition';
|
||||
|
||||
CREATE FUNCTION
|
||||
hypopg_partition_table(IN tablename regclass, IN partition_by_clause text)
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT VOLATILE COSt 100
|
||||
AS '$libdir/hypopg', 'hypopg_partition_table';
|
||||
|
||||
CREATE FUNCTION hypopg_reset_table()
|
||||
RETURNS void
|
||||
LANGUAGE C VOLATILE COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_reset_table';
|
||||
|
||||
CREATE FUNCTION hypopg_table(OUT relid oid, OUT tablename text,
|
||||
OUT parentid oid,
|
||||
OUT partition_by_clause text, OUT partition_of_clause text)
|
||||
RETURNS SETOF record
|
||||
LANGUAGE c COST 100
|
||||
AS '$libdir/hypopg', 'hypopg_table';
|
||||
|
|
|
|||
843
hypopg_import.c
843
hypopg_import.c
|
|
@ -16,17 +16,33 @@
|
|||
#if PG_VERSION_NUM >= 90300
|
||||
#include "access/htup_details.h"
|
||||
#endif
|
||||
#if PG_VERSION_NUM >= 100000
|
||||
#include "access/sysattr.h"
|
||||
#endif
|
||||
#include "catalog/heap.h"
|
||||
#include "catalog/namespace.h"
|
||||
#if PG_VERSION_NUM >= 100000
|
||||
#include "catalog/pg_am.h"
|
||||
#endif
|
||||
#include "catalog/pg_opclass.h"
|
||||
#include "commands/defrem.h"
|
||||
#if PG_VERSION_NUM < 90500
|
||||
#include "lib/stringinfo.h"
|
||||
#endif
|
||||
#include "nodes/makefuncs.h"
|
||||
#if PG_VERSION_NUM >= 100000
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "utils/ruleutils.h"
|
||||
#endif
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/planner.h"
|
||||
#include "optimizer/var.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parser.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/rel.h"
|
||||
|
|
@ -341,3 +357,830 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
|
|||
}
|
||||
ReleaseSysCache(ht_opc);
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 100000
|
||||
/*
|
||||
* Copied from src/backend/commands/tablecmds.c, not exported.
|
||||
*
|
||||
* Transform any expressions present in the partition key
|
||||
*
|
||||
* Returns a transformed PartitionSpec, as well as the strategy code
|
||||
*/
|
||||
PartitionSpec *
|
||||
transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
|
||||
{
|
||||
PartitionSpec *newspec;
|
||||
ParseState *pstate;
|
||||
RangeTblEntry *rte;
|
||||
ListCell *l;
|
||||
|
||||
newspec = makeNode(PartitionSpec);
|
||||
|
||||
newspec->strategy = partspec->strategy;
|
||||
newspec->partParams = NIL;
|
||||
newspec->location = partspec->location;
|
||||
|
||||
/* Parse partitioning strategy name */
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
if (pg_strcasecmp(partspec->strategy, "hash") == 0)
|
||||
*strategy = PARTITION_STRATEGY_HASH;
|
||||
else
|
||||
#endif
|
||||
if (pg_strcasecmp(partspec->strategy, "list") == 0)
|
||||
*strategy = PARTITION_STRATEGY_LIST;
|
||||
else if (pg_strcasecmp(partspec->strategy, "range") == 0)
|
||||
*strategy = PARTITION_STRATEGY_RANGE;
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("unrecognized partitioning strategy \"%s\"",
|
||||
partspec->strategy)));
|
||||
|
||||
/* Check valid number of columns for strategy */
|
||||
if (*strategy == PARTITION_STRATEGY_LIST &&
|
||||
list_length(partspec->partParams) != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("cannot use \"list\" partition strategy with more than one column")));
|
||||
|
||||
/*
|
||||
* Create a dummy ParseState and insert the target relation as its sole
|
||||
* rangetable entry. We need a ParseState for transformExpr.
|
||||
*/
|
||||
pstate = make_parsestate(NULL);
|
||||
rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
|
||||
addRTEtoQuery(pstate, rte, true, true, true);
|
||||
|
||||
/* take care of any partition expressions */
|
||||
foreach(l, partspec->partParams)
|
||||
{
|
||||
PartitionElem *pelem = castNode(PartitionElem, lfirst(l));
|
||||
ListCell *lc;
|
||||
|
||||
/* Check for PARTITION BY ... (foo, foo) */
|
||||
foreach(lc, newspec->partParams)
|
||||
{
|
||||
PartitionElem *pparam = castNode(PartitionElem, lfirst(lc));
|
||||
|
||||
if (pelem->name && pparam->name &&
|
||||
strcmp(pelem->name, pparam->name) == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
||||
errmsg("column \"%s\" appears more than once in partition key",
|
||||
pelem->name),
|
||||
parser_errposition(pstate, pelem->location)));
|
||||
}
|
||||
|
||||
if (pelem->expr)
|
||||
{
|
||||
/* Copy, to avoid scribbling on the input */
|
||||
pelem = copyObject(pelem);
|
||||
|
||||
/* Now do parse transformation of the expression */
|
||||
pelem->expr = transformExpr(pstate, pelem->expr,
|
||||
EXPR_KIND_PARTITION_EXPRESSION);
|
||||
|
||||
/* we have to fix its collations too */
|
||||
assign_expr_collations(pstate, pelem->expr);
|
||||
}
|
||||
|
||||
newspec->partParams = lappend(newspec->partParams, pelem);
|
||||
}
|
||||
|
||||
return newspec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/commands/tablecmds.c, not exported.
|
||||
*
|
||||
* Compute per-partition-column information from a list of PartitionElems.
|
||||
* Expressions in the PartitionElems must be parse-analyzed already.
|
||||
*/
|
||||
void
|
||||
ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
|
||||
List **partexprs, Oid *partopclass, Oid *partcollation,
|
||||
char strategy)
|
||||
{
|
||||
int attn;
|
||||
ListCell *lc;
|
||||
Oid am_oid;
|
||||
|
||||
attn = 0;
|
||||
foreach(lc, partParams)
|
||||
{
|
||||
PartitionElem *pelem = castNode(PartitionElem, lfirst(lc));
|
||||
Oid atttype;
|
||||
Oid attcollation;
|
||||
|
||||
if (pelem->name != NULL)
|
||||
{
|
||||
/* Simple attribute reference */
|
||||
HeapTuple atttuple;
|
||||
Form_pg_attribute attform;
|
||||
|
||||
atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
|
||||
pelem->name);
|
||||
if (!HeapTupleIsValid(atttuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column \"%s\" named in partition key does not exist",
|
||||
pelem->name)));
|
||||
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
|
||||
|
||||
if (attform->attnum <= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("cannot use system column \"%s\" in partition key",
|
||||
pelem->name)));
|
||||
|
||||
partattrs[attn] = attform->attnum;
|
||||
atttype = attform->atttypid;
|
||||
attcollation = attform->attcollation;
|
||||
ReleaseSysCache(atttuple);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Expression */
|
||||
Node *expr = pelem->expr;
|
||||
|
||||
Assert(expr != NULL);
|
||||
atttype = exprType(expr);
|
||||
attcollation = exprCollation(expr);
|
||||
|
||||
/*
|
||||
* Strip any top-level COLLATE clause. This ensures that we treat
|
||||
* "x COLLATE y" and "(x COLLATE y)" alike.
|
||||
*/
|
||||
while (IsA(expr, CollateExpr))
|
||||
expr = (Node *) ((CollateExpr *) expr)->arg;
|
||||
|
||||
if (IsA(expr, Var) &&
|
||||
((Var *) expr)->varattno > 0)
|
||||
{
|
||||
/*
|
||||
* User wrote "(column)" or "(column COLLATE something)".
|
||||
* Treat it like simple attribute anyway.
|
||||
*/
|
||||
partattrs[attn] = ((Var *) expr)->varattno;
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmapset *expr_attrs = NULL;
|
||||
int i;
|
||||
|
||||
partattrs[attn] = 0; /* marks the column as expression */
|
||||
*partexprs = lappend(*partexprs, expr);
|
||||
|
||||
/*
|
||||
* Try to simplify the expression before checking for
|
||||
* mutability. The main practical value of doing it in this
|
||||
* order is that an inline-able SQL-language function will be
|
||||
* accepted if its expansion is immutable, whether or not the
|
||||
* function itself is marked immutable.
|
||||
*
|
||||
* Note that expression_planner does not change the passed in
|
||||
* expression destructively and we have already saved the
|
||||
* expression to be stored into the catalog above.
|
||||
*/
|
||||
expr = (Node *) expression_planner((Expr *) expr);
|
||||
|
||||
/*
|
||||
* Partition expression cannot contain mutable functions,
|
||||
* because a given row must always map to the same partition
|
||||
* as long as there is no change in the partition boundary
|
||||
* structure.
|
||||
*/
|
||||
if (contain_mutable_functions(expr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("functions in partition key expression must be marked IMMUTABLE")));
|
||||
|
||||
/*
|
||||
* transformPartitionSpec() should have already rejected
|
||||
* subqueries, aggregates, window functions, and SRFs, based
|
||||
* on the EXPR_KIND_ for partition expressions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cannot have expressions containing whole-row references or
|
||||
* system column references.
|
||||
*/
|
||||
pull_varattnos(expr, 1, &expr_attrs);
|
||||
if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
|
||||
expr_attrs))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("partition key expressions cannot contain whole-row references")));
|
||||
for (i = FirstLowInvalidHeapAttributeNumber; i < 0; i++)
|
||||
{
|
||||
if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
|
||||
expr_attrs))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("partition key expressions cannot contain system column references")));
|
||||
}
|
||||
|
||||
/*
|
||||
* While it is not exactly *wrong* for a partition expression
|
||||
* to be a constant, it seems better to reject such keys.
|
||||
*/
|
||||
if (IsA(expr, Const))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("cannot use constant expression as partition key")));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply collation override if any
|
||||
*/
|
||||
if (pelem->collation)
|
||||
attcollation = get_collation_oid(pelem->collation, false);
|
||||
|
||||
/*
|
||||
* Check we have a collation iff it's a collatable type. The only
|
||||
* expected failures here are (1) COLLATE applied to a noncollatable
|
||||
* type, or (2) partition expression had an unresolved collation. But
|
||||
* we might as well code this to be a complete consistency check.
|
||||
*/
|
||||
if (type_is_collatable(atttype))
|
||||
{
|
||||
if (!OidIsValid(attcollation))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INDETERMINATE_COLLATION),
|
||||
errmsg("could not determine which collation to use for partition expression"),
|
||||
errhint("Use the COLLATE clause to set the collation explicitly.")));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OidIsValid(attcollation))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("collations are not supported by type %s",
|
||||
format_type_be(atttype))));
|
||||
}
|
||||
|
||||
partcollation[attn] = attcollation;
|
||||
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
/*
|
||||
* Identify the appropriate operator class. For list and range
|
||||
* partitioning, we use a btree operator class; hash partitioning uses
|
||||
* a hash operator class.
|
||||
*/
|
||||
if (strategy == PARTITION_STRATEGY_HASH)
|
||||
am_oid = HASH_AM_OID;
|
||||
else
|
||||
#endif
|
||||
am_oid = BTREE_AM_OID;
|
||||
|
||||
if (!pelem->opclass)
|
||||
{
|
||||
partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
|
||||
|
||||
if (!OidIsValid(partopclass[attn]))
|
||||
{
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
if (strategy == PARTITION_STRATEGY_HASH)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("data type %s has no default hash operator class",
|
||||
format_type_be(atttype)),
|
||||
errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
|
||||
else
|
||||
#endif
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("data type %s has no default btree operator class",
|
||||
format_type_be(atttype)),
|
||||
errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
partopclass[attn] = ResolveOpClass(pelem->opclass,
|
||||
atttype,
|
||||
am_oid == HASH_AM_OID ? "hash" : "btree",
|
||||
am_oid);
|
||||
|
||||
attn++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/utils/adt/ruleutils.c, not exported.
|
||||
*
|
||||
* get_relation_name
|
||||
* Get the unqualified name of a relation specified by OID
|
||||
*
|
||||
* This differs from the underlying get_rel_name() function in that it will
|
||||
* throw error instead of silently returning NULL if the OID is bad.
|
||||
*/
|
||||
char *
|
||||
get_relation_name(Oid relid)
|
||||
{
|
||||
char *relname = get_rel_name(relid);
|
||||
|
||||
if (!relname)
|
||||
elog(ERROR, "cache lookup failed for relation %u", relid);
|
||||
return relname;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/utils/adt/ruleutils.c, not exported.
|
||||
*
|
||||
* Helper function to identify node types that satisfy func_expr_windowless.
|
||||
* If in doubt, "false" is always a safe answer.
|
||||
*/
|
||||
bool
|
||||
looks_like_function(Node *node)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false; /* probably shouldn't happen */
|
||||
switch (nodeTag(node))
|
||||
{
|
||||
case T_FuncExpr:
|
||||
/* OK, unless it's going to deparse as a cast */
|
||||
return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL);
|
||||
case T_NullIfExpr:
|
||||
case T_CoalesceExpr:
|
||||
case T_MinMaxExpr:
|
||||
case T_SQLValueFunction:
|
||||
case T_XmlExpr:
|
||||
/* these are all accepted by func_expr_common_subexpr */
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/catalog/partition.c, not exported
|
||||
*
|
||||
* qsort_partition_hbound_cmp
|
||||
*
|
||||
* We sort hash bounds by modulus, then by remainder.
|
||||
*/
|
||||
int32
|
||||
qsort_partition_hbound_cmp(const void *a, const void *b)
|
||||
{
|
||||
PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
|
||||
PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
|
||||
|
||||
return partition_hbound_cmp(h1->modulus, h1->remainder,
|
||||
h2->modulus, h2->remainder);
|
||||
}
|
||||
|
||||
/*
|
||||
* partition_hbound_cmp
|
||||
*
|
||||
* Compares modulus first, then remainder if modulus are equal.
|
||||
*/
|
||||
int32
|
||||
partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
|
||||
{
|
||||
if (modulus1 < modulus2)
|
||||
return -1;
|
||||
if (modulus1 > modulus2)
|
||||
return 1;
|
||||
if (modulus1 == modulus2 && remainder1 != remainder2)
|
||||
return (remainder1 > remainder2) ? 1 : -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/catalog/partition.c, not exported
|
||||
*
|
||||
* qsort_partition_list_value_cmp
|
||||
*
|
||||
* Compare two list partition bound datums
|
||||
*/
|
||||
int32
|
||||
qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
|
||||
{
|
||||
Datum val1 = (*(const PartitionListValue **) a)->value,
|
||||
val2 = (*(const PartitionListValue **) b)->value;
|
||||
PartitionKey key = (PartitionKey) arg;
|
||||
|
||||
return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
|
||||
key->partcollation[0],
|
||||
val1, val2));
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/catalog/partition.c, not exported
|
||||
*
|
||||
* make_one_range_bound
|
||||
*
|
||||
* Return a PartitionRangeBound given a list of PartitionRangeDatum elements
|
||||
* and a flag telling whether the bound is lower or not. Made into a function
|
||||
* because there are multiple sites that want to use this facility.
|
||||
*/
|
||||
PartitionRangeBound *
|
||||
make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
|
||||
{
|
||||
PartitionRangeBound *bound;
|
||||
ListCell *lc;
|
||||
int i;
|
||||
|
||||
Assert(datums != NIL);
|
||||
|
||||
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
|
||||
bound->index = index;
|
||||
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
|
||||
bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
|
||||
sizeof(PartitionRangeDatumKind));
|
||||
bound->lower = lower;
|
||||
|
||||
i = 0;
|
||||
foreach(lc, datums)
|
||||
{
|
||||
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
|
||||
|
||||
/* What's contained in this range datum? */
|
||||
bound->kind[i] = datum->kind;
|
||||
|
||||
if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
|
||||
{
|
||||
Const *val = castNode(Const, datum->value);
|
||||
|
||||
if (val->constisnull)
|
||||
elog(ERROR, "invalid range bound datum");
|
||||
bound->datums[i] = val->constvalue;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return bound;
|
||||
}
|
||||
/*
|
||||
* Copied from src/backend/catalog/partition.c, not exported
|
||||
*
|
||||
* Used when sorting range bounds across all range partitions
|
||||
*/
|
||||
int32
|
||||
qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
|
||||
{
|
||||
PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
|
||||
PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
|
||||
PartitionKey key = (PartitionKey) arg;
|
||||
|
||||
return partition_rbound_cmp(key, b1->datums, b1->kind, b1->lower, b2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/catalog/partition.c, not exported
|
||||
*
|
||||
* partition_rbound_cmp
|
||||
*
|
||||
* Return for two range bounds whether the 1st one (specified in datums1,
|
||||
* kind1, and lower1) is <, =, or > the bound specified in *b2.
|
||||
*
|
||||
* Note that if the values of the two range bounds compare equal, then we take
|
||||
* into account whether they are upper or lower bounds, and an upper bound is
|
||||
* considered to be smaller than a lower bound. This is important to the way
|
||||
* that RelationBuildPartitionDesc() builds the PartitionBoundInfoData
|
||||
* structure, which only stores the upper bound of a common boundary between
|
||||
* two contiguous partitions.
|
||||
*/
|
||||
int32
|
||||
partition_rbound_cmp(PartitionKey key,
|
||||
Datum *datums1, PartitionRangeDatumKind *kind1,
|
||||
bool lower1, PartitionRangeBound *b2)
|
||||
{
|
||||
int32 cmpval = 0; /* placate compiler */
|
||||
int i;
|
||||
Datum *datums2 = b2->datums;
|
||||
PartitionRangeDatumKind *kind2 = b2->kind;
|
||||
bool lower2 = b2->lower;
|
||||
|
||||
for (i = 0; i < key->partnatts; i++)
|
||||
{
|
||||
/*
|
||||
* First, handle cases where the column is unbounded, which should not
|
||||
* invoke the comparison procedure, and should not consider any later
|
||||
* columns. Note that the PartitionRangeDatumKind enum elements
|
||||
* compare the same way as the values they represent.
|
||||
*/
|
||||
if (kind1[i] < kind2[i])
|
||||
return -1;
|
||||
else if (kind1[i] > kind2[i])
|
||||
return 1;
|
||||
else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
|
||||
|
||||
/*
|
||||
* The column bounds are both MINVALUE or both MAXVALUE. No later
|
||||
* columns should be considered, but we still need to compare
|
||||
* whether they are upper or lower bounds.
|
||||
*/
|
||||
break;
|
||||
|
||||
cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
|
||||
key->partcollation[i],
|
||||
datums1[i],
|
||||
datums2[i]));
|
||||
if (cmpval != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the comparison is anything other than equal, we're done. If they
|
||||
* compare equal though, we still have to consider whether the boundaries
|
||||
* are inclusive or exclusive. Exclusive one is considered smaller of the
|
||||
* two.
|
||||
*/
|
||||
if (cmpval == 0 && lower1 != lower2)
|
||||
cmpval = lower1 ? 1 : -1;
|
||||
|
||||
return cmpval;
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* Copied from src/backend/utils/adt/ruleutils.c, not exported.
|
||||
*
|
||||
* get_const_expr
|
||||
*
|
||||
* Make a string representation of a Const
|
||||
*
|
||||
* showtype can be -1 to never show "::typename" decoration, or +1 to always
|
||||
* show it, or 0 to show it only if the constant wouldn't be assumed to be
|
||||
* the right type by default.
|
||||
*
|
||||
* If the Const's collation isn't default for its type, show that too.
|
||||
* We mustn't do this when showtype is -1 (since that means the caller will
|
||||
* print "::typename", and we can't put a COLLATE clause in between). It's
|
||||
* caller's responsibility that collation isn't missed in such cases.
|
||||
* ----------
|
||||
*/
|
||||
void
|
||||
get_const_expr(Const *constval, deparse_context *context, int showtype)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
Oid typoutput;
|
||||
bool typIsVarlena;
|
||||
char *extval;
|
||||
bool needlabel = false;
|
||||
|
||||
if (constval->constisnull)
|
||||
{
|
||||
/*
|
||||
* Always label the type of a NULL constant to prevent misdecisions
|
||||
* about type when reparsing.
|
||||
*/
|
||||
appendStringInfoString(buf, "NULL");
|
||||
if (showtype >= 0)
|
||||
{
|
||||
appendStringInfo(buf, "::%s",
|
||||
format_type_with_typemod(constval->consttype,
|
||||
constval->consttypmod));
|
||||
get_const_collation(constval, context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
getTypeOutputInfo(constval->consttype,
|
||||
&typoutput, &typIsVarlena);
|
||||
|
||||
extval = OidOutputFunctionCall(typoutput, constval->constvalue);
|
||||
|
||||
switch (constval->consttype)
|
||||
{
|
||||
case INT4OID:
|
||||
|
||||
/*
|
||||
* INT4 can be printed without any decoration, unless it is
|
||||
* negative; in that case print it as '-nnn'::integer to ensure
|
||||
* that the output will re-parse as a constant, not as a constant
|
||||
* plus operator. In most cases we could get away with printing
|
||||
* (-nnn) instead, because of the way that gram.y handles negative
|
||||
* literals; but that doesn't work for INT_MIN, and it doesn't
|
||||
* seem that much prettier anyway.
|
||||
*/
|
||||
if (extval[0] != '-')
|
||||
appendStringInfoString(buf, extval);
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, "'%s'", extval);
|
||||
needlabel = true; /* we must attach a cast */
|
||||
}
|
||||
break;
|
||||
|
||||
case NUMERICOID:
|
||||
|
||||
/*
|
||||
* NUMERIC can be printed without quotes if it looks like a float
|
||||
* constant (not an integer, and not Infinity or NaN) and doesn't
|
||||
* have a leading sign (for the same reason as for INT4).
|
||||
*/
|
||||
if (isdigit((unsigned char) extval[0]) &&
|
||||
strcspn(extval, "eE.") != strlen(extval))
|
||||
{
|
||||
appendStringInfoString(buf, extval);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, "'%s'", extval);
|
||||
needlabel = true; /* we must attach a cast */
|
||||
}
|
||||
break;
|
||||
|
||||
case BITOID:
|
||||
case VARBITOID:
|
||||
appendStringInfo(buf, "B'%s'", extval);
|
||||
break;
|
||||
|
||||
case BOOLOID:
|
||||
if (strcmp(extval, "t") == 0)
|
||||
appendStringInfoString(buf, "true");
|
||||
else
|
||||
appendStringInfoString(buf, "false");
|
||||
break;
|
||||
|
||||
default:
|
||||
simple_quote_literal(buf, extval);
|
||||
break;
|
||||
}
|
||||
|
||||
pfree(extval);
|
||||
|
||||
if (showtype < 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* For showtype == 0, append ::typename unless the constant will be
|
||||
* implicitly typed as the right type when it is read in.
|
||||
*
|
||||
* XXX this code has to be kept in sync with the behavior of the parser,
|
||||
* especially make_const.
|
||||
*/
|
||||
switch (constval->consttype)
|
||||
{
|
||||
case BOOLOID:
|
||||
case UNKNOWNOID:
|
||||
/* These types can be left unlabeled */
|
||||
needlabel = false;
|
||||
break;
|
||||
case INT4OID:
|
||||
/* We determined above whether a label is needed */
|
||||
break;
|
||||
case NUMERICOID:
|
||||
|
||||
/*
|
||||
* Float-looking constants will be typed as numeric, which we
|
||||
* checked above; but if there's a nondefault typmod we need to
|
||||
* show it.
|
||||
*/
|
||||
needlabel |= (constval->consttypmod >= 0);
|
||||
break;
|
||||
default:
|
||||
needlabel = true;
|
||||
break;
|
||||
}
|
||||
if (needlabel || showtype > 0)
|
||||
appendStringInfo(buf, "::%s",
|
||||
format_type_with_typemod(constval->consttype,
|
||||
constval->consttypmod));
|
||||
|
||||
get_const_collation(constval, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/utils/adt/ruleutils.c, not exported.
|
||||
*
|
||||
* helper for get_const_expr: append COLLATE if needed
|
||||
*/
|
||||
void
|
||||
get_const_collation(Const *constval, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
|
||||
if (OidIsValid(constval->constcollid))
|
||||
{
|
||||
Oid typcollation = get_typcollation(constval->consttype);
|
||||
|
||||
if (constval->constcollid != typcollation)
|
||||
{
|
||||
appendStringInfo(buf, " COLLATE %s",
|
||||
generate_collation_name(constval->constcollid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/utils/adt/ruleutils.c, not exported.
|
||||
*
|
||||
* simple_quote_literal - Format a string as a SQL literal, append to buf
|
||||
*/
|
||||
void
|
||||
simple_quote_literal(StringInfo buf, const char *val)
|
||||
{
|
||||
const char *valptr;
|
||||
|
||||
/*
|
||||
* We form the string literal according to the prevailing setting of
|
||||
* standard_conforming_strings; we never use E''. User is responsible for
|
||||
* making sure result is used correctly.
|
||||
*/
|
||||
appendStringInfoChar(buf, '\'');
|
||||
for (valptr = val; *valptr; valptr++)
|
||||
{
|
||||
char ch = *valptr;
|
||||
|
||||
if (SQL_STR_DOUBLE(ch, !standard_conforming_strings))
|
||||
appendStringInfoChar(buf, ch);
|
||||
appendStringInfoChar(buf, ch);
|
||||
}
|
||||
appendStringInfoChar(buf, '\'');
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/parser/parse_utilcmd.c, not exported
|
||||
*
|
||||
* Transform one constant in a partition bound spec
|
||||
*/
|
||||
Const *
|
||||
transformPartitionBoundValue(ParseState *pstate, A_Const *con,
|
||||
const char *colName, Oid colType, int32 colTypmod)
|
||||
{
|
||||
Node *value;
|
||||
|
||||
/* Make it into a Const */
|
||||
value = (Node *) make_const(pstate, &con->val, con->location);
|
||||
|
||||
/* Coerce to correct type */
|
||||
value = coerce_to_target_type(pstate,
|
||||
value, exprType(value),
|
||||
colType,
|
||||
colTypmod,
|
||||
COERCION_ASSIGNMENT,
|
||||
COERCE_IMPLICIT_CAST,
|
||||
-1);
|
||||
|
||||
if (value == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("specified value cannot be cast to type %s for column \"%s\"",
|
||||
format_type_be(colType), colName),
|
||||
parser_errposition(pstate, con->location)));
|
||||
|
||||
/* Simplify the expression, in case we had a coercion */
|
||||
if (!IsA(value, Const))
|
||||
value = (Node *) expression_planner((Expr *) value);
|
||||
|
||||
/* Fail if we don't have a constant (i.e., non-immutable coercion) */
|
||||
if (!IsA(value, Const))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("specified value cannot be cast to type %s for column \"%s\"",
|
||||
format_type_be(colType), colName),
|
||||
errdetail("The cast requires a non-immutable conversion."),
|
||||
errhint("Try putting the literal value in single quotes."),
|
||||
parser_errposition(pstate, con->location)));
|
||||
|
||||
return (Const *) value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from src/backend/parser/parse_utilcmd.c, not exported
|
||||
*
|
||||
* validateInfiniteBounds
|
||||
*
|
||||
* Check that a MAXVALUE or MINVALUE specification in a partition bound is
|
||||
* followed only by more of the same.
|
||||
*/
|
||||
void
|
||||
validateInfiniteBounds(ParseState *pstate, List *blist)
|
||||
{
|
||||
ListCell *lc;
|
||||
PartitionRangeDatumKind kind = PARTITION_RANGE_DATUM_VALUE;
|
||||
|
||||
foreach(lc, blist)
|
||||
{
|
||||
PartitionRangeDatum *prd = castNode(PartitionRangeDatum, lfirst(lc));
|
||||
|
||||
if (kind == prd->kind)
|
||||
continue;
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case PARTITION_RANGE_DATUM_VALUE:
|
||||
kind = prd->kind;
|
||||
break;
|
||||
|
||||
case PARTITION_RANGE_DATUM_MAXVALUE:
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("every bound following MAXVALUE must also be MAXVALUE"),
|
||||
parser_errposition(pstate, exprLocation((Node *) prd))));
|
||||
|
||||
case PARTITION_RANGE_DATUM_MINVALUE:
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("every bound following MINVALUE must also be MINVALUE"),
|
||||
parser_errposition(pstate, exprLocation((Node *) prd))));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
1978
hypopg_table.c
Normal file
1978
hypopg_table.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -35,4 +35,88 @@ extern char *get_am_name(Oid amOid);
|
|||
#endif
|
||||
extern void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf);
|
||||
|
||||
#if PG_VERSION_NUM >= 100000
|
||||
/*
|
||||
* Imported from src/backend/catalog/partition.c, not exported
|
||||
*/
|
||||
typedef struct PartitionBoundInfoData
|
||||
{
|
||||
char strategy; /* hash, list or range? */
|
||||
int ndatums; /* Length of the datums following array */
|
||||
Datum **datums;
|
||||
PartitionRangeDatumKind **kind; /* The kind of each range bound datum;
|
||||
* NULL for hash and list partitioned
|
||||
* tables */
|
||||
int *indexes; /* Partition indexes */
|
||||
int null_index; /* Index of the null-accepting partition; -1
|
||||
* if there isn't one */
|
||||
int default_index; /* Index of the default partition; -1 if there
|
||||
* isn't one */
|
||||
} PartitionBoundInfoData;
|
||||
|
||||
/* One bound of a hash partition */
|
||||
typedef struct PartitionHashBound
|
||||
{
|
||||
int modulus;
|
||||
int remainder;
|
||||
int index;
|
||||
} PartitionHashBound;
|
||||
|
||||
/* One value coming from some (index'th) list partition */
|
||||
typedef struct PartitionListValue
|
||||
{
|
||||
int index;
|
||||
Datum value;
|
||||
} PartitionListValue;
|
||||
|
||||
/* One bound of a range partition */
|
||||
typedef struct PartitionRangeBound
|
||||
{
|
||||
int index;
|
||||
Datum *datums; /* range bound datums */
|
||||
PartitionRangeDatumKind *kind; /* the kind of each datum */
|
||||
bool lower; /* this is the lower (vs upper) bound */
|
||||
} PartitionRangeBound;
|
||||
|
||||
/* Context info needed for invoking a recursive querytree display routine */
|
||||
typedef struct
|
||||
{
|
||||
StringInfo buf; /* output buffer to append to */
|
||||
// List *namespaces; /* List of deparse_namespace nodes */
|
||||
// List *windowClause; /* Current query level's WINDOW clause */
|
||||
// List *windowTList; /* targetlist for resolving WINDOW clause */
|
||||
// int prettyFlags; /* enabling of pretty-print functions */
|
||||
// int wrapColumn; /* max line length, or -1 for no limit */
|
||||
// int indentLevel; /* current indent level for prettyprint */
|
||||
// bool varprefix; /* true to print prefixes on Vars */
|
||||
// ParseExprKind special_exprkind; /* set only for exprkinds needing special
|
||||
// * handling */
|
||||
} deparse_context;
|
||||
|
||||
|
||||
PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec,
|
||||
char *strategy);
|
||||
void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber
|
||||
*partattrs, List **partexprs, Oid *partopclass, Oid *partcollation,
|
||||
char strategy);
|
||||
char *get_relation_name(Oid relid);
|
||||
bool looks_like_function(Node *node);
|
||||
int32 qsort_partition_hbound_cmp(const void *a, const void *b);
|
||||
int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int
|
||||
remainder2);
|
||||
int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
|
||||
PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List
|
||||
*datums, bool lower);
|
||||
int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
|
||||
int32 partition_rbound_cmp(PartitionKey key,
|
||||
Datum *datums1, PartitionRangeDatumKind *kind1,
|
||||
bool lower1, PartitionRangeBound *b2);
|
||||
void get_const_expr(Const *constval, deparse_context *context, int
|
||||
showtype);
|
||||
void get_const_collation(Const *constval, deparse_context *context);
|
||||
void simple_quote_literal(StringInfo buf, const char *val);
|
||||
Const *transformPartitionBoundValue(ParseState *pstate, A_Const *con,
|
||||
const char *colName, Oid colType, int32 colTypmod);
|
||||
void validateInfiniteBounds(ParseState *pstate, List *blist);
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
|||
69
include/hypopg_table.h
Normal file
69
include/hypopg_table.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* hypopg_table.h: Implementation of hypothetical partitioning for PostgreSQL
|
||||
*
|
||||
* This file contains all includes for 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
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef _HYPOPG_TABLE_H_
|
||||
#define _HYPOPG_TABLE_H_
|
||||
|
||||
#if PG_VERSION_NUM >= 100000
|
||||
#define HYPO_PARTITION_NOT_SUPPORTED
|
||||
|
||||
#define HYPO_TABLE_NB_COLS 5 /* # of column hypopg_table() returns */
|
||||
#define HYPO_ADD_PART_COLS 2 /* # of column hypopg_add_partition() returns */
|
||||
|
||||
|
||||
/*--- Structs --- */
|
||||
|
||||
/*--------------------------------------------------------
|
||||
* Hypothetical partition storage, pretty much the needed data from RelOptInfo.
|
||||
* Some dynamic informations such as pages and lines are not stored but
|
||||
* computed when the hypothetical partition is used.
|
||||
*/
|
||||
typedef struct hypoTable
|
||||
{
|
||||
Oid oid; /* hypothetical table unique identifier */
|
||||
Oid parentid; /* In case of partition, it's direct parent,
|
||||
otherwise InvalidOid */
|
||||
char *tablename; /* hypothetical partition name, or original
|
||||
table name for root parititon */
|
||||
Oid namespace; /* Oid of the hypothetical table's schema */
|
||||
|
||||
/* used for partitioned relations */
|
||||
PartitionScheme part_scheme; /* Partitioning scheme. */
|
||||
|
||||
/* added for internal use */
|
||||
PartitionBoundSpec *boundspec; /* Needed to generate the PartitionDesc and
|
||||
PartitionBoundInfo */
|
||||
PartitionKey partkey; /* Needed to generate the partition key
|
||||
expressions and deparsing */
|
||||
Oid *partopclass; /* oid of partkey's element opclass, needed for
|
||||
deparsing the key */
|
||||
} hypoTable;
|
||||
|
||||
/* List of hypothetic partitions for current backend */
|
||||
extern List *hypoTables;
|
||||
#else
|
||||
#define HYPO_PARTITION_NOT_SUPPORTED() elog(ERROR, "hypopg: Hypothetical partitioning requires PostgreSQl 10 or above"); PG_RETURN_VOID();
|
||||
#endif
|
||||
|
||||
/*--- Functions --- */
|
||||
|
||||
void hypo_table_reset(void);
|
||||
|
||||
Datum hypopg_table(PG_FUNCTION_ARGS);
|
||||
Datum hypopg_add_partition(PG_FUNCTION_ARGS);
|
||||
Datum hypopg_drop_table(PG_FUNCTION_ARGS);
|
||||
Datum hypopg_partition_table(PG_FUNCTION_ARGS);
|
||||
Datum hypopg_reset_table(PG_FUNCTION_ARGS);
|
||||
|
||||
#endif
|
||||
Loading…
Reference in a new issue