/* * CloneForeignKeyConstraints * Clone foreign keys from a partitioned table to a newly acquired * partition. * * relationId is a partition of parentId, so we can be certain that it has the * same columns with the same datatypes. The columns may be in different * order, though. * * The *cloned list is appended ClonedConstraint elements describing what was * created. */ void CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned) { Relation pg_constraint; Relation parentRel; Relation rel; ScanKeyData key; SysScanDesc scan; TupleDesc tupdesc; HeapTuple tuple; AttrNumber *attmap; parentRel = heap_open(parentId, NoLock); /* already got lock */ /* see ATAddForeignKeyConstraint about lock level */ rel = heap_open(relationId, AccessExclusiveLock); pg_constraint = heap_open(ConstraintRelationId, RowShareLock); tupdesc = RelationGetDescr(pg_constraint); /* * The constraint key may differ, if the columns in the partition are * different. This map is used to convert them. */ attmap = convert_tuples_by_name_map(RelationGetDescr(rel), RelationGetDescr(parentRel), gettext_noop("could not convert row type")); ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentId)); scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, NULL, 1, &key); while ((tuple = systable_getnext(scan)) != NULL) { Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple); AttrNumber conkey[INDEX_MAX_KEYS]; AttrNumber mapped_conkey[INDEX_MAX_KEYS]; AttrNumber confkey[INDEX_MAX_KEYS]; Oid conpfeqop[INDEX_MAX_KEYS]; Oid conppeqop[INDEX_MAX_KEYS]; Oid conffeqop[INDEX_MAX_KEYS]; Constraint *fkconstraint; ClonedConstraint *newc; Oid constrOid; ObjectAddress parentAddr, childAddr; int nelem; int i; ArrayType *arr; Datum datum; bool isnull; /* only foreign keys */ if (constrForm->contype != CONSTRAINT_FOREIGN) continue; ObjectAddressSet(parentAddr, ConstraintRelationId, HeapTupleGetOid(tuple)); datum = fastgetattr(tuple, Anum_pg_constraint_conkey, tupdesc, &isnull); if (isnull) elog(ERROR, "null conkey"); arr = DatumGetArrayTypeP(datum); nelem = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelem < 1 || nelem > INDEX_MAX_KEYS || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != INT2OID) elog(ERROR, "conkey is not a 1-D smallint array"); memcpy(conkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber)); for (i = 0; i < nelem; i++) mapped_conkey[i] = attmap[conkey[i] - 1]; datum = fastgetattr(tuple, Anum_pg_constraint_confkey, tupdesc, &isnull); if (isnull) elog(ERROR, "null confkey"); arr = DatumGetArrayTypeP(datum); nelem = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelem < 1 || nelem > INDEX_MAX_KEYS || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != INT2OID) elog(ERROR, "confkey is not a 1-D smallint array"); memcpy(confkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber)); datum = fastgetattr(tuple, Anum_pg_constraint_conpfeqop, tupdesc, &isnull); if (isnull) elog(ERROR, "null conpfeqop"); arr = DatumGetArrayTypeP(datum); nelem = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelem < 1 || nelem > INDEX_MAX_KEYS || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conpfeqop is not a 1-D OID array"); memcpy(conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); datum = fastgetattr(tuple, Anum_pg_constraint_conpfeqop, tupdesc, &isnull); if (isnull) elog(ERROR, "null conpfeqop"); arr = DatumGetArrayTypeP(datum); nelem = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelem < 1 || nelem > INDEX_MAX_KEYS || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conpfeqop is not a 1-D OID array"); memcpy(conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); datum = fastgetattr(tuple, Anum_pg_constraint_conppeqop, tupdesc, &isnull); if (isnull) elog(ERROR, "null conppeqop"); arr = DatumGetArrayTypeP(datum); nelem = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelem < 1 || nelem > INDEX_MAX_KEYS || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conppeqop is not a 1-D OID array"); memcpy(conppeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); datum = fastgetattr(tuple, Anum_pg_constraint_conffeqop, tupdesc, &isnull); if (isnull) elog(ERROR, "null conffeqop"); arr = DatumGetArrayTypeP(datum); nelem = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelem < 1 || nelem > INDEX_MAX_KEYS || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conffeqop is not a 1-D OID array"); memcpy(conffeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); constrOid = CreateConstraintEntry(NameStr(constrForm->conname), constrForm->connamespace, CONSTRAINT_FOREIGN, constrForm->condeferrable, constrForm->condeferred, constrForm->convalidated, HeapTupleGetOid(tuple), relationId, mapped_conkey, nelem, nelem, InvalidOid, /* not a domain constraint */ constrForm->conindid, /* same index */ constrForm->confrelid, /* same foreign rel */ confkey, conpfeqop, conppeqop, conffeqop, nelem, constrForm->confupdtype, constrForm->confdeltype, constrForm->confmatchtype, NULL, NULL, NULL, NULL, false, 1, false, true); ObjectAddressSet(childAddr, ConstraintRelationId, constrOid); recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO); fkconstraint = makeNode(Constraint); /* for now this is all we need */ fkconstraint->fk_upd_action = constrForm->confupdtype; fkconstraint->fk_del_action = constrForm->confdeltype; fkconstraint->deferrable = constrForm->condeferrable; fkconstraint->initdeferred = constrForm->condeferred; createForeignKeyTriggers(rel, constrForm->confrelid, fkconstraint, constrOid, constrForm->conindid, false); if (cloned) { /* * Feed back caller about the constraints we created, so that they can * set up constraint verification. */ newc = palloc(sizeof(ClonedConstraint)); newc->relid = relationId; newc->refrelid = constrForm->confrelid; newc->conindid = constrForm->conindid; newc->conid = constrOid; newc->constraint = fkconstraint; *cloned = lappend(*cloned, newc); } } systable_endscan(scan); pfree(attmap); if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { PartitionDesc partdesc = RelationGetPartitionDesc(rel); int i; for (i = 0; i < partdesc->nparts; i++) CloneForeignKeyConstraints(RelationGetRelid(rel), partdesc->oids[i], cloned); } heap_close(rel, NoLock); /* keep lock till commit */ heap_close(parentRel, NoLock); heap_close(pg_constraint, RowShareLock); }
/* * 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); }