summaryrefslogtreecommitdiffstats
path: root/src/backend/catalog/partition.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/catalog/partition.c')
-rw-r--r--src/backend/catalog/partition.c390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..790f4cc
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,390 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/attmap.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/table.h"
+#include "catalog/indexing.h"
+#include "catalog/partition.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_partitioned_table.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/optimizer.h"
+#include "partitioning/partbounds.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/fmgroids.h"
+#include "utils/partcache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+static Oid get_partition_parent_worker(Relation inhRel, Oid relid,
+ bool *detach_pending);
+static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
+ List **ancestors);
+
+/*
+ * get_partition_parent
+ * Obtain direct parent of given relation
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * If the partition is in the process of being detached, an error is thrown,
+ * unless even_if_detached is passed as true.
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid, bool even_if_detached)
+{
+ Relation catalogRelation;
+ Oid result;
+ bool detach_pending;
+
+ catalogRelation = table_open(InheritsRelationId, AccessShareLock);
+
+ result = get_partition_parent_worker(catalogRelation, relid,
+ &detach_pending);
+
+ if (!OidIsValid(result))
+ elog(ERROR, "could not find tuple for parent of relation %u", relid);
+
+ if (detach_pending && !even_if_detached)
+ elog(ERROR, "relation %u has no parent because it's being detached",
+ relid);
+
+ table_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_partition_parent_worker
+ * Scan the pg_inherits relation to return the OID of the parent of the
+ * given relation
+ *
+ * If the partition is being detached, *detach_pending is set true (but the
+ * original parent is still returned.)
+ */
+static Oid
+get_partition_parent_worker(Relation inhRel, Oid relid, bool *detach_pending)
+{
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ Oid result = InvalidOid;
+ HeapTuple tuple;
+
+ *detach_pending = false;
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+ tuple = systable_getnext(scan);
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_inherits form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ /* Let caller know of partition being detached */
+ if (form->inhdetachpending)
+ *detach_pending = true;
+ result = form->inhparent;
+ }
+
+ systable_endscan(scan);
+
+ return result;
+}
+
+/*
+ * get_partition_ancestors
+ * Obtain ancestors of given relation
+ *
+ * Returns a list of ancestors of the given relation.
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument and each ancestor will have precisely one parent, it should
+ * only be called when it is known that the relation is a partition.
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+ List *result = NIL;
+ Relation inhRel;
+
+ inhRel = table_open(InheritsRelationId, AccessShareLock);
+
+ get_partition_ancestors_worker(inhRel, relid, &result);
+
+ table_close(inhRel, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_partition_ancestors_worker
+ * recursive worker for get_partition_ancestors
+ */
+static void
+get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
+{
+ Oid parentOid;
+ bool detach_pending;
+
+ /*
+ * Recursion ends at the topmost level, ie., when there's no parent; also
+ * when the partition is being detached.
+ */
+ parentOid = get_partition_parent_worker(inhRel, relid, &detach_pending);
+ if (parentOid == InvalidOid || detach_pending)
+ return;
+
+ *ancestors = lappend_oid(*ancestors, parentOid);
+ get_partition_ancestors_worker(inhRel, parentOid, ancestors);
+}
+
+/*
+ * index_get_partition
+ * Return the OID of index of the given partition that is a child
+ * of the given index, or InvalidOid if there isn't one.
+ */
+Oid
+index_get_partition(Relation partition, Oid indexId)
+{
+ List *idxlist = RelationGetIndexList(partition);
+ ListCell *l;
+
+ foreach(l, idxlist)
+ {
+ Oid partIdx = lfirst_oid(l);
+ HeapTuple tup;
+ Form_pg_class classForm;
+ bool ispartition;
+
+ tup = SearchSysCache1(RELOID, ObjectIdGetDatum(partIdx));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for relation %u", partIdx);
+ classForm = (Form_pg_class) GETSTRUCT(tup);
+ ispartition = classForm->relispartition;
+ ReleaseSysCache(tup);
+ if (!ispartition)
+ continue;
+ if (get_partition_parent(partIdx, false) == indexId)
+ {
+ list_free(idxlist);
+ return partIdx;
+ }
+ }
+
+ list_free(idxlist);
+ return InvalidOid;
+}
+
+/*
+ * map_partition_varattnos - maps varattnos of all Vars in 'expr' (that have
+ * varno 'fromrel_varno') from the attnums of 'from_rel' to the attnums of
+ * 'to_rel', each of which may be either a leaf partition or a partitioned
+ * table, but both of which must be from the same partitioning hierarchy.
+ *
+ * We need this because even though all of the same column names must be
+ * present in all relations in the hierarchy, and they must also have the
+ * same types, the attnums may be different.
+ *
+ * Note: this will work on any node tree, so really the argument and result
+ * should be declared "Node *". But a substantial majority of the callers
+ * are working on Lists, so it's less messy to do the casts internally.
+ */
+List *
+map_partition_varattnos(List *expr, int fromrel_varno,
+ Relation to_rel, Relation from_rel)
+{
+ if (expr != NIL)
+ {
+ AttrMap *part_attmap;
+ bool found_whole_row;
+
+ part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
+ RelationGetDescr(from_rel));
+ expr = (List *) map_variable_attnos((Node *) expr,
+ fromrel_varno, 0,
+ part_attmap,
+ RelationGetForm(to_rel)->reltype,
+ &found_whole_row);
+ /* Since we provided a to_rowtype, we may ignore found_whole_row. */
+ }
+
+ return expr;
+}
+
+/*
+ * Checks if any of the 'attnums' is a partition key attribute for rel
+ *
+ * Sets *used_in_expr if any of the 'attnums' is found to be referenced in some
+ * partition key expression. It's possible for a column to be both used
+ * directly and as part of an expression; if that happens, *used_in_expr may
+ * end up as either true or false. That's OK for current uses of this
+ * function, because *used_in_expr is only used to tailor the error message
+ * text.
+ */
+bool
+has_partition_attrs(Relation rel, Bitmapset *attnums, bool *used_in_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexprs_item;
+ int i;
+
+ if (attnums == NULL || rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (bms_is_member(partattno - FirstLowInvalidHeapAttributeNumber,
+ attnums))
+ {
+ if (used_in_expr)
+ *used_in_expr = false;
+ return true;
+ }
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+
+ /* Find all attributes referenced */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs, partexprs_item);
+
+ if (bms_overlap(attnums, expr_attrs))
+ {
+ if (used_in_expr)
+ *used_in_expr = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * get_default_partition_oid
+ *
+ * Given a relation OID, return the OID of the default partition, if one
+ * exists. Use get_default_oid_from_partdesc where possible, for
+ * efficiency.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+ HeapTuple tuple;
+ Oid defaultPartId = InvalidOid;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_partitioned_table part_table_form;
+
+ part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ defaultPartId = part_table_form->partdefid;
+ ReleaseSysCache(tuple);
+ }
+
+ return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Update pg_partitioned_table.partdefid with a new default partition OID.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+ HeapTuple tuple;
+ Relation pg_partitioned_table;
+ Form_pg_partitioned_table part_table_form;
+
+ pg_partitioned_table = table_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u",
+ parentId);
+
+ part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ part_table_form->partdefid = defaultPartId;
+ CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+ heap_freetuple(tuple);
+ table_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * get_proposed_default_constraint
+ *
+ * This function returns the negation of new_part_constraints, which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_proposed_default_constraint(List *new_part_constraints)
+{
+ Expr *defPartConstraint;
+
+ defPartConstraint = make_ands_explicit(new_part_constraints);
+
+ /*
+ * Derive the partition constraints of default partition by negating the
+ * given partition constraints. The partition constraint never evaluates
+ * to NULL, so negating it like this is safe.
+ */
+ defPartConstraint = makeBoolExpr(NOT_EXPR,
+ list_make1(defPartConstraint),
+ -1);
+
+ /* Simplify, to put the negated expression into canonical form */
+ defPartConstraint =
+ (Expr *) eval_const_expressions(NULL,
+ (Node *) defPartConstraint);
+ defPartConstraint = canonicalize_qual(defPartConstraint, true);
+
+ return make_ands_implicit(defPartConstraint);
+}