summaryrefslogtreecommitdiff
path: root/src/backend/optimizer/util/inherit.c
diff options
context:
space:
mode:
authorAlvaro Herrera <alvherre@alvh.no-ip.org>2019-01-10 14:54:31 -0300
committerAlvaro Herrera <alvherre@alvh.no-ip.org>2019-01-10 14:54:31 -0300
commitb60c39759908bb2a2dbcfc108ec19bdbdcc278e8 (patch)
treebdfa3231c4a98e58ab0fae1d36d9117658fc2d06 /src/backend/optimizer/util/inherit.c
parentdacadcd1f32873d930b3953d3055dc7cb9548e48 (diff)
Move inheritance expansion code into its own file
This commit moves expand_inherited_tables and underlings from optimizer/prep/prepunionc.c to optimizer/utils/inherit.c. Also, all of the AppendRelInfo-based expression manipulation routines are moved to optimizer/utils/appendinfo.c. No functional code changes. One exception is the introduction of make_append_rel_info, but that's still just moving around code. Also, stop including <limits.h> in prepunion.c, which no longer needs it since 3fc6e2d7f5b6. I (Álvaro) noticed this because Amit was copying that to inherit.c, which likewise doesn't need it. Author: Amit Langote Discussion: https://postgr.es/m/3be67028-a00a-502c-199a-da00eec8fb6e@lab.ntt.co.jp
Diffstat (limited to 'src/backend/optimizer/util/inherit.c')
-rw-r--r--src/backend/optimizer/util/inherit.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
new file mode 100644
index 00000000000..350e6afe270
--- /dev/null
+++ b/src/backend/optimizer/util/inherit.c
@@ -0,0 +1,439 @@
+/*-------------------------------------------------------------------------
+ *
+ * inherit.c
+ * Routines to process child relations in inheritance trees
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/inherit.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/partition.h"
+#include "catalog/pg_inherits.h"
+#include "miscadmin.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/inherit.h"
+#include "optimizer/planner.h"
+#include "optimizer/prep.h"
+#include "utils/rel.h"
+
+
+static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti);
+static void expand_partitioned_rtentry(PlannerInfo *root,
+ RangeTblEntry *parentrte,
+ Index parentRTindex, Relation parentrel,
+ PlanRowMark *top_parentrc, LOCKMODE lockmode,
+ List **appinfos);
+static void expand_single_inheritance_child(PlannerInfo *root,
+ RangeTblEntry *parentrte,
+ Index parentRTindex, Relation parentrel,
+ PlanRowMark *top_parentrc, Relation childrel,
+ List **appinfos, RangeTblEntry **childrte_p,
+ Index *childRTindex_p);
+
+
+/*
+ * expand_inherited_tables
+ * Expand each rangetable entry that represents an inheritance set
+ * into an "append relation". At the conclusion of this process,
+ * the "inh" flag is set in all and only those RTEs that are append
+ * relation parents.
+ */
+void
+expand_inherited_tables(PlannerInfo *root)
+{
+ Index nrtes;
+ Index rti;
+ ListCell *rl;
+
+ /*
+ * expand_inherited_rtentry may add RTEs to parse->rtable. The function is
+ * expected to recursively handle any RTEs that it creates with inh=true.
+ * So just scan as far as the original end of the rtable list.
+ */
+ nrtes = list_length(root->parse->rtable);
+ rl = list_head(root->parse->rtable);
+ for (rti = 1; rti <= nrtes; rti++)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
+
+ expand_inherited_rtentry(root, rte, rti);
+ rl = lnext(rl);
+ }
+}
+
+/*
+ * expand_inherited_rtentry
+ * Check whether a rangetable entry represents an inheritance set.
+ * If so, add entries for all the child tables to the query's
+ * rangetable, and build AppendRelInfo nodes for all the child tables
+ * and add them to root->append_rel_list. If not, clear the entry's
+ * "inh" flag to prevent later code from looking for AppendRelInfos.
+ *
+ * Note that the original RTE is considered to represent the whole
+ * inheritance set. The first of the generated RTEs is an RTE for the same
+ * table, but with inh = false, to represent the parent table in its role
+ * as a simple member of the inheritance set.
+ *
+ * A childless table is never considered to be an inheritance set. For
+ * regular inheritance, a parent RTE must always have at least two associated
+ * AppendRelInfos: one corresponding to the parent table as a simple member of
+ * inheritance set and one or more corresponding to the actual children.
+ * Since a partitioned table is not scanned, it might have only one associated
+ * AppendRelInfo.
+ */
+static void
+expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
+{
+ Oid parentOID;
+ PlanRowMark *oldrc;
+ Relation oldrelation;
+ LOCKMODE lockmode;
+ List *inhOIDs;
+ ListCell *l;
+
+ /* Does RT entry allow inheritance? */
+ if (!rte->inh)
+ return;
+ /* Ignore any already-expanded UNION ALL nodes */
+ if (rte->rtekind != RTE_RELATION)
+ {
+ Assert(rte->rtekind == RTE_SUBQUERY);
+ return;
+ }
+ /* Fast path for common case of childless table */
+ parentOID = rte->relid;
+ if (!has_subclass(parentOID))
+ {
+ /* Clear flag before returning */
+ rte->inh = false;
+ return;
+ }
+
+ /*
+ * The rewriter should already have obtained an appropriate lock on each
+ * relation named in the query. However, for each child relation we add
+ * to the query, we must obtain an appropriate lock, because this will be
+ * the first use of those relations in the parse/rewrite/plan pipeline.
+ * Child rels should use the same lockmode as their parent.
+ */
+ lockmode = rte->rellockmode;
+
+ /* Scan for all members of inheritance set, acquire needed locks */
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+
+ /*
+ * Check that there's at least one descendant, else treat as no-child
+ * case. This could happen despite above has_subclass() check, if table
+ * once had a child but no longer does.
+ */
+ if (list_length(inhOIDs) < 2)
+ {
+ /* Clear flag before returning */
+ rte->inh = false;
+ return;
+ }
+
+ /*
+ * If parent relation is selected FOR UPDATE/SHARE, we need to mark its
+ * PlanRowMark as isParent = true, and generate a new PlanRowMark for each
+ * child.
+ */
+ oldrc = get_plan_rowmark(root->rowMarks, rti);
+ if (oldrc)
+ oldrc->isParent = true;
+
+ /*
+ * Must open the parent relation to examine its tupdesc. We need not lock
+ * it; we assume the rewriter already did.
+ */
+ oldrelation = heap_open(parentOID, NoLock);
+
+ /* Scan the inheritance set and expand it */
+ if (RelationGetPartitionDesc(oldrelation) != NULL)
+ {
+ Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
+
+ /*
+ * If this table has partitions, recursively expand them in the order
+ * in which they appear in the PartitionDesc. While at it, also
+ * extract the partition key columns of all the partitioned tables.
+ */
+ expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc,
+ lockmode, &root->append_rel_list);
+ }
+ else
+ {
+ List *appinfos = NIL;
+ RangeTblEntry *childrte;
+ Index childRTindex;
+
+ /*
+ * This table has no partitions. Expand any plain inheritance
+ * children in the order the OIDs were returned by
+ * find_all_inheritors.
+ */
+ foreach(l, inhOIDs)
+ {
+ Oid childOID = lfirst_oid(l);
+ Relation newrelation;
+
+ /* Open rel if needed; we already have required locks */
+ if (childOID != parentOID)
+ newrelation = heap_open(childOID, NoLock);
+ else
+ newrelation = oldrelation;
+
+ /*
+ * It is possible that the parent table has children that are temp
+ * tables of other backends. We cannot safely access such tables
+ * (because of buffering issues), and the best thing to do seems
+ * to be to silently ignore them.
+ */
+ if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation))
+ {
+ heap_close(newrelation, lockmode);
+ continue;
+ }
+
+ expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc,
+ newrelation,
+ &appinfos, &childrte,
+ &childRTindex);
+
+ /* Close child relations, but keep locks */
+ if (childOID != parentOID)
+ heap_close(newrelation, NoLock);
+ }
+
+ /*
+ * If all the children were temp tables, pretend it's a
+ * non-inheritance situation; we don't need Append node in that case.
+ * The duplicate RTE we added for the parent table is harmless, so we
+ * don't bother to get rid of it; ditto for the useless PlanRowMark
+ * node.
+ */
+ if (list_length(appinfos) < 2)
+ rte->inh = false;
+ else
+ root->append_rel_list = list_concat(root->append_rel_list,
+ appinfos);
+
+ }
+
+ heap_close(oldrelation, NoLock);
+}
+
+/*
+ * expand_partitioned_rtentry
+ * Recursively expand an RTE for a partitioned table.
+ */
+static void
+expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
+ Index parentRTindex, Relation parentrel,
+ PlanRowMark *top_parentrc, LOCKMODE lockmode,
+ List **appinfos)
+{
+ int i;
+ RangeTblEntry *childrte;
+ Index childRTindex;
+ PartitionDesc partdesc = RelationGetPartitionDesc(parentrel);
+
+ check_stack_depth();
+
+ /* A partitioned table should always have a partition descriptor. */
+ Assert(partdesc);
+
+ Assert(parentrte->inh);
+
+ /*
+ * Note down whether any partition key cols are being updated. Though it's
+ * the root partitioned table's updatedCols we are interested in, we
+ * instead use parentrte to get the updatedCols. This is convenient
+ * because parentrte already has the root partrel's updatedCols translated
+ * to match the attribute ordering of parentrel.
+ */
+ if (!root->partColsUpdated)
+ root->partColsUpdated =
+ has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
+
+ /* First expand the partitioned table itself. */
+ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel,
+ top_parentrc, parentrel,
+ appinfos, &childrte, &childRTindex);
+
+ /*
+ * If the partitioned table has no partitions, treat this as the
+ * non-inheritance case.
+ */
+ if (partdesc->nparts == 0)
+ {
+ parentrte->inh = false;
+ return;
+ }
+
+ for (i = 0; i < partdesc->nparts; i++)
+ {
+ Oid childOID = partdesc->oids[i];
+ Relation childrel;
+
+ /* Open rel; we already have required locks */
+ childrel = heap_open(childOID, NoLock);
+
+ /*
+ * Temporary partitions belonging to other sessions should have been
+ * disallowed at definition, but for paranoia's sake, let's double
+ * check.
+ */
+ if (RELATION_IS_OTHER_TEMP(childrel))
+ elog(ERROR, "temporary relation from another session found as partition");
+
+ expand_single_inheritance_child(root, parentrte, parentRTindex,
+ parentrel, top_parentrc, childrel,
+ appinfos, &childrte, &childRTindex);
+
+ /* If this child is itself partitioned, recurse */
+ if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ expand_partitioned_rtentry(root, childrte, childRTindex,
+ childrel, top_parentrc, lockmode,
+ appinfos);
+
+ /* Close child relation, but keep locks */
+ heap_close(childrel, NoLock);
+ }
+}
+
+/*
+ * expand_single_inheritance_child
+ * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus
+ * maybe a PlanRowMark.
+ *
+ * We now expand the partition hierarchy level by level, creating a
+ * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each
+ * partitioned descendant acts as a parent of its immediate partitions.
+ * (This is a difference from what older versions of PostgreSQL did and what
+ * is still done in the case of table inheritance for unpartitioned tables,
+ * where the hierarchy is flattened during RTE expansion.)
+ *
+ * PlanRowMarks still carry the top-parent's RTI, and the top-parent's
+ * allMarkTypes field still accumulates values from all descendents.
+ *
+ * "parentrte" and "parentRTindex" are immediate parent's RTE and
+ * RTI. "top_parentrc" is top parent's PlanRowMark.
+ *
+ * The child RangeTblEntry and its RTI are returned in "childrte_p" and
+ * "childRTindex_p" resp.
+ */
+static void
+expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
+ Index parentRTindex, Relation parentrel,
+ PlanRowMark *top_parentrc, Relation childrel,
+ List **appinfos, RangeTblEntry **childrte_p,
+ Index *childRTindex_p)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel);
+ Oid childOID = RelationGetRelid(childrel);
+ RangeTblEntry *childrte;
+ Index childRTindex;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list. We
+ * copy most fields of the parent's RTE, but replace relation OID and
+ * relkind, and set inh = false. Also, set requiredPerms to zero since
+ * all required permissions checks are done on the original RTE. Likewise,
+ * set the child's securityQuals to empty, because we only want to apply
+ * the parent's RLS conditions regardless of what RLS properties
+ * individual children may have. (This is an intentional choice to make
+ * inherited RLS work like regular permissions checks.) The parent
+ * securityQuals will be propagated to children along with other base
+ * restriction clauses, so we don't need to do it here.
+ */
+ childrte = copyObject(parentrte);
+ *childrte_p = childrte;
+ childrte->relid = childOID;
+ childrte->relkind = childrel->rd_rel->relkind;
+ /* A partitioned child will need to be expanded further. */
+ if (childOID != parentOID &&
+ childrte->relkind == RELKIND_PARTITIONED_TABLE)
+ childrte->inh = true;
+ else
+ childrte->inh = false;
+ childrte->requiredPerms = 0;
+ childrte->securityQuals = NIL;
+ parse->rtable = lappend(parse->rtable, childrte);
+ childRTindex = list_length(parse->rtable);
+ *childRTindex_p = childRTindex;
+
+ /*
+ * We need an AppendRelInfo if paths will be built for the child RTE. If
+ * childrte->inh is true, then we'll always need to generate append paths
+ * for it. If childrte->inh is false, we must scan it if it's not a
+ * partitioned table; but if it is a partitioned table, then it never has
+ * any data of its own and need not be scanned.
+ */
+ if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh)
+ {
+ appinfo = make_append_rel_info(parentrel, childrel,
+ parentRTindex, childRTindex);
+ *appinfos = lappend(*appinfos, appinfo);
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
+ appinfo->translated_vars);
+ childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
+ appinfo->translated_vars);
+ childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
+ appinfo->translated_vars);
+ }
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (top_parentrc)
+ {
+ PlanRowMark *childrc = makeNode(PlanRowMark);
+
+ childrc->rti = childRTindex;
+ childrc->prti = top_parentrc->rti;
+ childrc->rowmarkId = top_parentrc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ childrc->markType = select_rowmark_type(childrte,
+ top_parentrc->strength);
+ childrc->allMarkTypes = (1 << childrc->markType);
+ childrc->strength = top_parentrc->strength;
+ childrc->waitPolicy = top_parentrc->waitPolicy;
+
+ /*
+ * We mark RowMarks for partitioned child tables as parent RowMarks so
+ * that the executor ignores them (except their existence means that
+ * the child tables be locked using appropriate mode).
+ */
+ childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE);
+
+ /* Include child's rowmark type in top parent's allMarkTypes */
+ top_parentrc->allMarkTypes |= childrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, childrc);
+ }
+}