/*------------------------------------------------------------------------- * * 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); }