mirror of
https://github.com/HypoPG/hypopg
synced 2026-05-24 09:38:21 +00:00
1567 lines
43 KiB
C
1567 lines
43 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* hypopg_import.c: Import of some PostgreSQL private fuctions.
|
|
*
|
|
* This program is open source, licensed under the PostgreSQL license.
|
|
* For license terms, see the LICENSE file.
|
|
*
|
|
* Copyright (c) 2008-2018, PostgreSQL Global Development Group
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
#if PG_VERSION_NUM >= 90300
|
|
#include "access/htup_details.h"
|
|
#endif
|
|
#if PG_VERSION_NUM >= 100000
|
|
#include "access/sysattr.h"
|
|
#include "access/stratnum.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"
|
|
#if PG_VERSION_NUM >= 110000
|
|
#include "catalog/partition.h"
|
|
#include "optimizer/cost.h"
|
|
#include "optimizer/paths.h"
|
|
#include "partitioning/partbounds.h"
|
|
#include "utils/partcache.h"
|
|
#include "partitioning/partdefs.h"
|
|
#endif
|
|
#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"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
#include "include/hypopg_import.h"
|
|
|
|
|
|
|
|
/* Copied from src/backend/optimizer/util/plancat.c, not exported.
|
|
*
|
|
* Build a targetlist representing the columns of the specified index.
|
|
* Each column is represented by a Var for the corresponding base-relation
|
|
* column, or an expression in base-relation Vars, as appropriate.
|
|
*
|
|
* There are never any dropped columns in indexes, so unlike
|
|
* build_physical_tlist, we need no failure case.
|
|
*/
|
|
List *
|
|
build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
|
|
Relation heapRelation)
|
|
{
|
|
List *tlist = NIL;
|
|
Index varno = index->rel->relid;
|
|
ListCell *indexpr_item;
|
|
int i;
|
|
|
|
indexpr_item = list_head(index->indexprs);
|
|
for (i = 0; i < index->ncolumns; i++)
|
|
{
|
|
int indexkey = index->indexkeys[i];
|
|
Expr *indexvar;
|
|
|
|
if (indexkey != 0)
|
|
{
|
|
/* simple column */
|
|
Form_pg_attribute att_tup;
|
|
|
|
if (indexkey < 0)
|
|
att_tup = SystemAttributeDefinition(indexkey,
|
|
heapRelation->rd_rel->relhasoids);
|
|
else
|
|
#if PG_VERSION_NUM >= 110000
|
|
att_tup = TupleDescAttr(heapRelation->rd_att, indexkey - 1);
|
|
#else
|
|
att_tup = heapRelation->rd_att->attrs[indexkey - 1];
|
|
#endif
|
|
|
|
indexvar = (Expr *) makeVar(varno,
|
|
indexkey,
|
|
att_tup->atttypid,
|
|
att_tup->atttypmod,
|
|
att_tup->attcollation,
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
/* expression column */
|
|
if (indexpr_item == NULL)
|
|
elog(ERROR, "wrong number of index expressions");
|
|
indexvar = (Expr *) lfirst(indexpr_item);
|
|
indexpr_item = lnext(indexpr_item);
|
|
}
|
|
|
|
tlist = lappend(tlist,
|
|
makeTargetEntry(indexvar,
|
|
i + 1,
|
|
NULL,
|
|
false));
|
|
}
|
|
if (indexpr_item != NULL)
|
|
elog(ERROR, "wrong number of index expressions");
|
|
|
|
return tlist;
|
|
}
|
|
|
|
/*
|
|
* Copied from src/backend/commands/indexcmds.c, not exported.
|
|
* Resolve possibly-defaulted operator class specification
|
|
*/
|
|
Oid
|
|
GetIndexOpClass(List *opclass, Oid attrType,
|
|
char *accessMethodName, Oid accessMethodId)
|
|
{
|
|
char *schemaname;
|
|
char *opcname;
|
|
HeapTuple tuple;
|
|
Oid opClassId,
|
|
opInputType;
|
|
|
|
/*
|
|
* Release 7.0 removed network_ops, timespan_ops, and datetime_ops, so we
|
|
* ignore those opclass names so the default *_ops is used. This can be
|
|
* removed in some later release. bjm 2000/02/07
|
|
*
|
|
* Release 7.1 removes lztext_ops, so suppress that too for a while. tgl
|
|
* 2000/07/30
|
|
*
|
|
* Release 7.2 renames timestamp_ops to timestamptz_ops, so suppress that
|
|
* too for awhile. I'm starting to think we need a better approach. tgl
|
|
* 2000/10/01
|
|
*
|
|
* Release 8.0 removes bigbox_ops (which was dead code for a long while
|
|
* anyway). tgl 2003/11/11
|
|
*/
|
|
if (list_length(opclass) == 1)
|
|
{
|
|
char *claname = strVal(linitial(opclass));
|
|
|
|
if (strcmp(claname, "network_ops") == 0 ||
|
|
strcmp(claname, "timespan_ops") == 0 ||
|
|
strcmp(claname, "datetime_ops") == 0 ||
|
|
strcmp(claname, "lztext_ops") == 0 ||
|
|
strcmp(claname, "timestamp_ops") == 0 ||
|
|
strcmp(claname, "bigbox_ops") == 0)
|
|
opclass = NIL;
|
|
}
|
|
|
|
if (opclass == NIL)
|
|
{
|
|
/* no operator class specified, so find the default */
|
|
opClassId = GetDefaultOpClass(attrType, accessMethodId);
|
|
if (!OidIsValid(opClassId))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("data type %s has no default operator class for access method \"%s\"",
|
|
format_type_be(attrType), accessMethodName),
|
|
errhint("You must specify an operator class for the index or define a default operator class for the data type.")));
|
|
return opClassId;
|
|
}
|
|
|
|
/*
|
|
* Specific opclass name given, so look up the opclass.
|
|
*/
|
|
|
|
/* deconstruct the name list */
|
|
DeconstructQualifiedName(opclass, &schemaname, &opcname);
|
|
|
|
if (schemaname)
|
|
{
|
|
/* Look in specific schema only */
|
|
Oid namespaceId;
|
|
|
|
#if PG_VERSION_NUM >= 90300
|
|
namespaceId = LookupExplicitNamespace(schemaname, false);
|
|
#else
|
|
namespaceId = LookupExplicitNamespace(schemaname);
|
|
#endif
|
|
tuple = SearchSysCache3(CLAAMNAMENSP,
|
|
ObjectIdGetDatum(accessMethodId),
|
|
PointerGetDatum(opcname),
|
|
ObjectIdGetDatum(namespaceId));
|
|
}
|
|
else
|
|
{
|
|
/* Unqualified opclass name, so search the search path */
|
|
opClassId = OpclassnameGetOpcid(accessMethodId, opcname);
|
|
if (!OidIsValid(opClassId))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
opcname, accessMethodName)));
|
|
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opClassId));
|
|
}
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
NameListToString(opclass), accessMethodName)));
|
|
}
|
|
|
|
/*
|
|
* Verify that the index operator class accepts this datatype. Note we
|
|
* will accept binary compatibility.
|
|
*/
|
|
opClassId = HeapTupleGetOid(tuple);
|
|
opInputType = ((Form_pg_opclass) GETSTRUCT(tuple))->opcintype;
|
|
|
|
if (!IsBinaryCoercible(attrType, opInputType))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("operator class \"%s\" does not accept data type %s",
|
|
NameListToString(opclass), format_type_be(attrType))));
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
return opClassId;
|
|
}
|
|
|
|
/*
|
|
* Copied from src/backend/commands/indexcmds.c, not exported.
|
|
* CheckPredicate
|
|
* Checks that the given partial-index predicate is valid.
|
|
*
|
|
* This used to also constrain the form of the predicate to forms that
|
|
* indxpath.c could do something with. However, that seems overly
|
|
* restrictive. One useful application of partial indexes is to apply
|
|
* a UNIQUE constraint across a subset of a table, and in that scenario
|
|
* any evaluatable predicate will work. So accept any predicate here
|
|
* (except ones requiring a plan), and let indxpath.c fend for itself.
|
|
*/
|
|
void
|
|
CheckPredicate(Expr *predicate)
|
|
{
|
|
/*
|
|
* transformExpr() should have already rejected subqueries, aggregates,
|
|
* and window functions, based on the EXPR_KIND_ for a predicate.
|
|
*/
|
|
|
|
/*
|
|
* A predicate using mutable functions is probably wrong, for the same
|
|
* reasons that we don't allow an index expression to use one.
|
|
*/
|
|
if (CheckMutability(predicate))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("functions in index predicate must be marked IMMUTABLE")));
|
|
}
|
|
|
|
/*
|
|
* Copied from src/backend/commands/indexcmds.c, not exported.
|
|
* CheckMutability
|
|
* Test whether given expression is mutable
|
|
*/
|
|
bool
|
|
CheckMutability(Expr *expr)
|
|
{
|
|
/*
|
|
* First run the expression through the planner. This has a couple of
|
|
* important consequences. First, function default arguments will get
|
|
* inserted, which may affect volatility (consider "default now()").
|
|
* Second, inline-able functions will get inlined, which may allow us to
|
|
* conclude that the function is really less volatile than it's marked. As
|
|
* an example, polymorphic functions must be marked with the most volatile
|
|
* behavior that they have for any input type, but once we inline the
|
|
* function we may be able to conclude that it's not so volatile for the
|
|
* particular input type we're dealing with.
|
|
*
|
|
* We assume here that expression_planner() won't scribble on its input.
|
|
*/
|
|
expr = expression_planner(expr);
|
|
|
|
/* Now we can search for non-immutable functions */
|
|
return contain_mutable_functions((Node *) expr);
|
|
}
|
|
|
|
#if PG_VERSION_NUM < 90500
|
|
/*
|
|
* Copied from src/backend/commands/amcmds.c
|
|
*
|
|
* get_am_name - given an access method OID name and type, look up its name.
|
|
*/
|
|
char *
|
|
get_am_name(Oid amOid)
|
|
{
|
|
HeapTuple tup;
|
|
char *result = NULL;
|
|
|
|
tup = SearchSysCache1(AMOID, ObjectIdGetDatum(amOid));
|
|
if (HeapTupleIsValid(tup))
|
|
{
|
|
Form_pg_am amform = (Form_pg_am) GETSTRUCT(tup);
|
|
|
|
result = pstrdup(NameStr(amform->amname));
|
|
ReleaseSysCache(tup);
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Copied from src/backend/utils/adt/ruleutils.c, not exported.
|
|
*
|
|
* get_opclass_name - fetch name of an index operator class
|
|
*
|
|
* The opclass name is appended (after a space) to buf.
|
|
*
|
|
* Output is suppressed if the opclass is the default for the given
|
|
* actual_datatype. (If you don't want this behavior, just pass
|
|
* InvalidOid for actual_datatype.)
|
|
*/
|
|
void
|
|
get_opclass_name(Oid opclass, Oid actual_datatype,
|
|
StringInfo buf)
|
|
{
|
|
HeapTuple ht_opc;
|
|
Form_pg_opclass opcrec;
|
|
char *opcname;
|
|
char *nspname;
|
|
|
|
ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
|
|
if (!HeapTupleIsValid(ht_opc))
|
|
elog(ERROR, "cache lookup failed for opclass %u", opclass);
|
|
opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
|
|
|
|
if (!OidIsValid(actual_datatype) ||
|
|
GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass)
|
|
{
|
|
/* Okay, we need the opclass name. Do we need to qualify it? */
|
|
opcname = NameStr(opcrec->opcname);
|
|
if (OpclassIsVisible(opclass))
|
|
appendStringInfo(buf, " %s", quote_identifier(opcname));
|
|
else
|
|
{
|
|
nspname = get_namespace_name(opcrec->opcnamespace);
|
|
appendStringInfo(buf, " %s.%s",
|
|
quote_identifier(nspname),
|
|
quote_identifier(opcname));
|
|
}
|
|
}
|
|
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->partnatts, key->partsupfunc,
|
|
key->partcollation, b1->datums, b1->kind,
|
|
b1->lower, b2);
|
|
}
|
|
|
|
/* ----------
|
|
* 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))));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Imported from src/backend/catalog/partition.c, not exported
|
|
*
|
|
* get_partition_operator
|
|
*
|
|
* Return oid of the operator of given strategy for a given partition key
|
|
* column.
|
|
*/
|
|
Oid
|
|
get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
|
|
bool *need_relabel)
|
|
{
|
|
Oid operoid;
|
|
|
|
/*
|
|
* First check if there exists an operator of the given strategy, with
|
|
* this column's type as both its lefttype and righttype, in the
|
|
* partitioning operator family specified for the column.
|
|
*/
|
|
operoid = get_opfamily_member(key->partopfamily[col],
|
|
key->parttypid[col],
|
|
key->parttypid[col],
|
|
strategy);
|
|
|
|
/*
|
|
* If one doesn't exist, we must resort to using an operator in the same
|
|
* operator family but with the operator class declared input type. It is
|
|
* OK to do so, because the column's type is known to be binary-coercible
|
|
* with the operator class input type (otherwise, the operator class in
|
|
* question would not have been accepted as the partitioning operator
|
|
* class). We must however inform the caller to wrap the non-Const
|
|
* expression with a RelabelType node to denote the implicit coercion. It
|
|
* ensures that the resulting expression structurally matches similarly
|
|
* processed expressions within the optimizer.
|
|
*/
|
|
if (!OidIsValid(operoid))
|
|
{
|
|
operoid = get_opfamily_member(key->partopfamily[col],
|
|
key->partopcintype[col],
|
|
key->partopcintype[col],
|
|
strategy);
|
|
if (!OidIsValid(operoid))
|
|
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
|
|
strategy, key->partopcintype[col], key->partopcintype[col],
|
|
key->partopfamily[col]);
|
|
*need_relabel = true;
|
|
}
|
|
else
|
|
*need_relabel = false;
|
|
|
|
return operoid;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copied from src/backend/catalog/partition.c, not exported
|
|
*
|
|
* make_partition_op_expr
|
|
* Returns an Expr for the given partition key column with arg1 and
|
|
* arg2 as its leftop and rightop, respectively
|
|
*/
|
|
Expr *
|
|
make_partition_op_expr(PartitionKey key, int keynum,
|
|
uint16 strategy, Expr *arg1, Expr *arg2)
|
|
{
|
|
Oid operoid;
|
|
bool need_relabel = false;
|
|
Expr *result = NULL;
|
|
|
|
/* Get the correct btree operator for this partitioning column */
|
|
operoid = get_partition_operator(key, keynum, strategy, &need_relabel);
|
|
|
|
/*
|
|
* Chosen operator may be such that the non-Const operand needs to be
|
|
* coerced, so apply the same; see the comment in
|
|
* get_partition_operator().
|
|
*/
|
|
if (!IsA(arg1, Const) &&
|
|
(need_relabel ||
|
|
key->partcollation[keynum] != key->parttypcoll[keynum]))
|
|
arg1 = (Expr *) makeRelabelType(arg1,
|
|
key->partopcintype[keynum],
|
|
-1,
|
|
key->partcollation[keynum],
|
|
COERCE_EXPLICIT_CAST);
|
|
|
|
/* Generate the actual expression */
|
|
switch (key->strategy)
|
|
{
|
|
case PARTITION_STRATEGY_LIST:
|
|
{
|
|
List *elems = (List *) arg2;
|
|
int nelems = list_length(elems);
|
|
|
|
Assert(nelems >= 1);
|
|
Assert(keynum == 0);
|
|
|
|
if (nelems > 1 &&
|
|
!type_is_array(key->parttypid[keynum]))
|
|
{
|
|
ArrayExpr *arrexpr;
|
|
ScalarArrayOpExpr *saopexpr;
|
|
|
|
/* Construct an ArrayExpr for the right-hand inputs */
|
|
arrexpr = makeNode(ArrayExpr);
|
|
arrexpr->array_typeid =
|
|
get_array_type(key->parttypid[keynum]);
|
|
arrexpr->array_collid = key->parttypcoll[keynum];
|
|
arrexpr->element_typeid = key->parttypid[keynum];
|
|
arrexpr->elements = elems;
|
|
arrexpr->multidims = false;
|
|
arrexpr->location = -1;
|
|
|
|
/* Build leftop = ANY (rightop) */
|
|
saopexpr = makeNode(ScalarArrayOpExpr);
|
|
saopexpr->opno = operoid;
|
|
saopexpr->opfuncid = get_opcode(operoid);
|
|
saopexpr->useOr = true;
|
|
saopexpr->inputcollid = key->partcollation[keynum];
|
|
saopexpr->args = list_make2(arg1, arrexpr);
|
|
saopexpr->location = -1;
|
|
|
|
result = (Expr *) saopexpr;
|
|
}
|
|
else
|
|
{
|
|
List *elemops = NIL;
|
|
ListCell *lc;
|
|
|
|
foreach (lc, elems)
|
|
{
|
|
Expr *elem = lfirst(lc),
|
|
*elemop;
|
|
|
|
elemop = make_opclause(operoid,
|
|
BOOLOID,
|
|
false,
|
|
arg1, elem,
|
|
InvalidOid,
|
|
key->partcollation[keynum]);
|
|
elemops = lappend(elemops, elemop);
|
|
}
|
|
|
|
result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PARTITION_STRATEGY_RANGE:
|
|
result = make_opclause(operoid,
|
|
BOOLOID,
|
|
false,
|
|
arg1, arg2,
|
|
InvalidOid,
|
|
key->partcollation[keynum]);
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "invalid partitioning strategy");
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Copied from src/backend/catalog/partition.c, not exported
|
|
*
|
|
* get_range_key_properties
|
|
* Returns range partition key information for a given column
|
|
*
|
|
* This is a subroutine for get_qual_for_range, and its API is pretty
|
|
* specialized to that caller.
|
|
*
|
|
* Constructs an Expr for the key column (returned in *keyCol) and Consts
|
|
* for the lower and upper range limits (returned in *lower_val and
|
|
* *upper_val). For MINVALUE/MAXVALUE limits, NULL is returned instead of
|
|
* a Const. All of these structures are freshly palloc'd.
|
|
*
|
|
* *partexprs_item points to the cell containing the next expression in
|
|
* the key->partexprs list, or NULL. It may be advanced upon return.
|
|
*/
|
|
void
|
|
get_range_key_properties(PartitionKey key, int keynum,
|
|
PartitionRangeDatum *ldatum,
|
|
PartitionRangeDatum *udatum,
|
|
ListCell **partexprs_item,
|
|
Expr **keyCol,
|
|
Const **lower_val, Const **upper_val)
|
|
{
|
|
/* Get partition key expression for this column */
|
|
if (key->partattrs[keynum] != 0)
|
|
{
|
|
*keyCol = (Expr *) makeVar(1,
|
|
key->partattrs[keynum],
|
|
key->parttypid[keynum],
|
|
key->parttypmod[keynum],
|
|
key->parttypcoll[keynum],
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
if (*partexprs_item == NULL)
|
|
elog(ERROR, "wrong number of partition key expressions");
|
|
*keyCol = copyObject(lfirst(*partexprs_item));
|
|
*partexprs_item = lnext(*partexprs_item);
|
|
}
|
|
|
|
/* Get appropriate Const nodes for the bounds */
|
|
if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
|
|
*lower_val = castNode(Const, copyObject(ldatum->value));
|
|
else
|
|
*lower_val = NULL;
|
|
|
|
if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
|
|
*upper_val = castNode(Const, copyObject(udatum->value));
|
|
else
|
|
*upper_val = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copied from src/backend/catalog/partition.c, not exported
|
|
*
|
|
* get_range_nulltest
|
|
*
|
|
* A non-default range partition table does not currently allow partition
|
|
* keys to be null, so emit an IS NOT NULL expression for each key column.
|
|
*/
|
|
List *
|
|
get_range_nulltest(PartitionKey key)
|
|
{
|
|
List *result = NIL;
|
|
NullTest *nulltest;
|
|
ListCell *partexprs_item;
|
|
int i;
|
|
|
|
partexprs_item = list_head(key->partexprs);
|
|
for (i = 0; i < key->partnatts; i++)
|
|
{
|
|
Expr *keyCol;
|
|
|
|
if (key->partattrs[i] != 0)
|
|
{
|
|
keyCol = (Expr *) makeVar(1,
|
|
key->partattrs[i],
|
|
key->parttypid[i],
|
|
key->parttypmod[i],
|
|
key->parttypcoll[i],
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
if (partexprs_item == NULL)
|
|
elog(ERROR, "wrong number of partition key expressions");
|
|
keyCol = copyObject(lfirst(partexprs_item));
|
|
partexprs_item = lnext(partexprs_item);
|
|
}
|
|
|
|
nulltest = makeNode(NullTest);
|
|
nulltest->arg = keyCol;
|
|
nulltest->nulltesttype = IS_NOT_NULL;
|
|
nulltest->argisrow = false;
|
|
nulltest->location = -1;
|
|
result = lappend(result, nulltest);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copied from src/backend/optimizer/prep/prepunion.c, not exported
|
|
*
|
|
* make_inh_translation_list
|
|
* Build the list of translations from parent Vars to child Vars for
|
|
* an inheritance child.
|
|
*
|
|
* For paranoia's sake, we match type/collation as well as attribute name.
|
|
*/
|
|
void
|
|
make_inh_translation_list(Relation oldrelation, Relation newrelation,
|
|
Index newvarno,
|
|
List **translated_vars)
|
|
{
|
|
List *vars = NIL;
|
|
TupleDesc old_tupdesc = RelationGetDescr(oldrelation);
|
|
TupleDesc new_tupdesc = RelationGetDescr(newrelation);
|
|
int oldnatts = old_tupdesc->natts;
|
|
int newnatts = new_tupdesc->natts;
|
|
int old_attno;
|
|
|
|
for (old_attno = 0; old_attno < oldnatts; old_attno++)
|
|
{
|
|
Form_pg_attribute att;
|
|
char *attname;
|
|
Oid atttypid;
|
|
int32 atttypmod;
|
|
Oid attcollation;
|
|
int new_attno;
|
|
|
|
att = TupleDescAttr(old_tupdesc, old_attno);
|
|
if (att->attisdropped)
|
|
{
|
|
/* Just put NULL into this list entry */
|
|
vars = lappend(vars, NULL);
|
|
continue;
|
|
}
|
|
attname = NameStr(att->attname);
|
|
atttypid = att->atttypid;
|
|
atttypmod = att->atttypmod;
|
|
attcollation = att->attcollation;
|
|
|
|
/*
|
|
* When we are generating the "translation list" for the parent table
|
|
* of an inheritance set, no need to search for matches.
|
|
*/
|
|
if (oldrelation == newrelation)
|
|
{
|
|
vars = lappend(vars, makeVar(newvarno,
|
|
(AttrNumber) (old_attno + 1),
|
|
atttypid,
|
|
atttypmod,
|
|
attcollation,
|
|
0));
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Otherwise we have to search for the matching column by name.
|
|
* There's no guarantee it'll have the same column position, because
|
|
* of cases like ALTER TABLE ADD COLUMN and multiple inheritance.
|
|
* However, in simple cases it will be the same column number, so try
|
|
* that before we go groveling through all the columns.
|
|
*
|
|
* Note: the test for (att = ...) != NULL cannot fail, it's just a
|
|
* notational device to include the assignment into the if-clause.
|
|
*/
|
|
if (old_attno < newnatts &&
|
|
(att = TupleDescAttr(new_tupdesc, old_attno)) != NULL &&
|
|
!att->attisdropped && att->attinhcount != 0 &&
|
|
strcmp(attname, NameStr(att->attname)) == 0)
|
|
new_attno = old_attno;
|
|
else
|
|
{
|
|
for (new_attno = 0; new_attno < newnatts; new_attno++)
|
|
{
|
|
att = TupleDescAttr(new_tupdesc, new_attno);
|
|
if (!att->attisdropped && att->attinhcount != 0 &&
|
|
strcmp(attname, NameStr(att->attname)) == 0)
|
|
break;
|
|
}
|
|
if (new_attno >= newnatts)
|
|
elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"",
|
|
attname, RelationGetRelationName(newrelation));
|
|
}
|
|
|
|
/* Found it, check type and collation match */
|
|
if (atttypid != att->atttypid || atttypmod != att->atttypmod)
|
|
elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type",
|
|
attname, RelationGetRelationName(newrelation));
|
|
if (attcollation != att->attcollation)
|
|
elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation",
|
|
attname, RelationGetRelationName(newrelation));
|
|
|
|
vars = lappend(vars, makeVar(newvarno,
|
|
(AttrNumber) (new_attno + 1),
|
|
atttypid,
|
|
atttypmod,
|
|
attcollation,
|
|
0));
|
|
}
|
|
|
|
*translated_vars = vars;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copied from src/backend/optimizer/path/allpaths.c, not exported
|
|
*
|
|
* set_plain_rel_pathlist
|
|
* Build access paths for a plain relation (no subquery, no inheritance)
|
|
*/
|
|
void
|
|
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
|
{
|
|
Relids required_outer;
|
|
|
|
/*
|
|
* We don't support pushing join clauses into the quals of a seqscan, but
|
|
* it could still have required parameterization due to LATERAL refs in
|
|
* its tlist.
|
|
*/
|
|
required_outer = rel->lateral_relids;
|
|
|
|
/* Consider sequential scan */
|
|
add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
|
|
|
|
/* If appropriate, consider parallel sequential scan */
|
|
if (rel->consider_parallel && required_outer == NULL)
|
|
create_plain_partial_paths(root, rel);
|
|
|
|
/* Consider index scans */
|
|
create_index_paths(root, rel);
|
|
|
|
/* Consider TID scans */
|
|
create_tidscan_paths(root, rel);
|
|
}
|
|
|
|
/*
|
|
* Copied from src/backend/optimizer/path/allpaths.c, not exported
|
|
*
|
|
* create_plain_partial_paths
|
|
* Build partial access paths for parallel scan of a plain relation
|
|
*/
|
|
void
|
|
create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
|
|
{
|
|
int parallel_workers;
|
|
|
|
parallel_workers = compute_parallel_worker(rel, rel->pages, -1,
|
|
max_parallel_workers_per_gather);
|
|
|
|
/* If any limit was set to zero, the user doesn't want a parallel scan. */
|
|
if (parallel_workers <= 0)
|
|
return;
|
|
|
|
/* Add an unordered partial path based on a parallel sequential scan. */
|
|
add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
|
|
}
|
|
|
|
|
|
#endif
|