hi. * The attached patch makes foreign keys with PERIOD fail if any of the foreign key columns is "generated columns".
* The following queries will cause segmentation fault. not sure the best way to fix it. the reason in LINE: numpks = transformColumnNameList(RelationGetRelid(pkrel), fkconstraint->pk_attrs, pkattnum, pktypoid); begin; drop table if exists temporal3,temporal_fk_rng2rng; CREATE TABLE temporal3 (id int4range,valid_at tsrange, CONSTRAINT temporal3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)); CREATE TABLE temporal_fk_rng2rng ( id int4range,valid_at tsrange,parent_id int4range, CONSTRAINT temporal_fk_rng2rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS), CONSTRAINT temporal_fk_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES temporal3 (id, valid_at) ); * change the function FindFKComparisonOperators's "eqstrategy" to make pg_constraint record correct {conpfeqop,conppeqop,conffeqop}. * fix the ON DELETE SET NULL/DEFAULT (columnlist). Now the following queries error will be more consistent. ALTER TABLE temporal_fk_rng2rng DROP CONSTRAINT temporal_fk_rng2rng_fk, ADD CONSTRAINT temporal_fk_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES temporal_rng ON DELETE SET DEFAULT(valid_at); --ON DELETE SET NULL(valid_at); * refactor restrict_cascading_range function. * you did if (numfks != numpks) before if (is_temporal) {numfks += 1;}, So I changed the code order to make the error report more consistent. anyway, I put it in one patch. please check the attached.
From 7dc2194f9bd46cfee7cfc774a2e52a2b64d465ea Mon Sep 17 00:00:00 2001 From: pgaddict <jian.universal...@gmail.com> Date: Sun, 29 Oct 2023 14:42:57 +0800 Subject: [PATCH v1 1/1] various small fixes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * The attached patch makes foreign keys with PERIOD fail if any of the foreign key columns is "generated columns". * change the function FindFKComparisonOperators's "eqstrategy" to make pg_constraint record correct {conpfeqop,conppeqop,conffeqop}. * refactor restrict_cascading_range function. * first (is_temporal) {numfks += 1;} then (numfks != numpks) validation. * variable comment fix. --- src/backend/commands/tablecmds.c | 96 ++++++++++++++++------------- src/backend/utils/adt/ri_triggers.c | 32 +++++----- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5ae0f113..86e99c81 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -522,7 +522,7 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra bool is_temporal); static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, int numfksetcols, const int16 *fksetcolsattnums, - List *fksetcols); + const int16 *fkperiodattnums, List *fksetcols); static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, int numfks, int16 *pkattnum, int16 *fkattnum, @@ -10292,6 +10292,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, fkdelsetcols, NULL); validateFkOnDeleteSetColumns(numfks, fkattnum, numfkdelsetcols, fkdelsetcols, + fkperiodattnums, fkconstraint->fk_del_set_cols); /* @@ -10325,6 +10326,43 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, opclasses); } + /* + * On the strength of a previous constraint, we might avoid scanning + * tables to validate this one. See below. + */ + old_check_ok = (fkconstraint->old_conpfeqop != NIL); + Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop)); + + for (i = 0; i < numpks; i++) + { + FindFKComparisonOperators( + fkconstraint, tab, i, fkattnum, + &old_check_ok, &old_pfeqop_item, + pktypoid[i], fktypoid[i], opclasses[i], + is_temporal, false, + &pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]); + } + /* set pfeq, ppeq, ffeq operators for withoutoverlaps constraint. + * this also assume overlaps is the last key columns in constraint. + * + */ + if (is_temporal) + { + pkattnum[numpks] = pkperiodattnums[0]; + pktypoid[numpks] = pkperiodtypoids[0]; + fkattnum[numpks] = fkperiodattnums[0]; + fktypoid[numpks] = fkperiodtypoids[0]; + + FindFKComparisonOperators( + fkconstraint, tab, numpks, fkattnum, + &old_check_ok, &old_pfeqop_item, + pkperiodtypoids[0], fkperiodtypoids[0], opclasses[numpks], + is_temporal, true, + &pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]); + numfks += 1; + numpks += 1; + } + /* * Now we can check permissions. */ @@ -10371,38 +10409,6 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, (errcode(ERRCODE_INVALID_FOREIGN_KEY), errmsg("number of referencing and referenced columns for foreign key disagree"))); - /* - * On the strength of a previous constraint, we might avoid scanning - * tables to validate this one. See below. - */ - old_check_ok = (fkconstraint->old_conpfeqop != NIL); - Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop)); - - for (i = 0; i < numpks; i++) - { - FindFKComparisonOperators( - fkconstraint, tab, i, fkattnum, - &old_check_ok, &old_pfeqop_item, - pktypoid[i], fktypoid[i], opclasses[i], - is_temporal, false, - &pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]); - } - if (is_temporal) { - pkattnum[numpks] = pkperiodattnums[0]; - pktypoid[numpks] = pkperiodtypoids[0]; - fkattnum[numpks] = fkperiodattnums[0]; - fktypoid[numpks] = fkperiodtypoids[0]; - - FindFKComparisonOperators( - fkconstraint, tab, numpks, fkattnum, - &old_check_ok, &old_pfeqop_item, - pkperiodtypoids[0], fkperiodtypoids[0], opclasses[numpks], - is_temporal, true, - &pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]); - numfks += 1; - numpks += 1; - } - /* * Create all the constraint and trigger objects, recursing to partitions * as necessary. First handle the referenced side. @@ -10455,11 +10461,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, int numfksetcols, const int16 *fksetcolsattnums, - List *fksetcols) + const int16 *fkperiodattnums, List *fksetcols) { for (int i = 0; i < numfksetcols; i++) { int16 setcol_attnum = fksetcolsattnums[i]; + /* assume only one PERIOD key column in foreign key */ + int16 fkperiod_attnum = fkperiodattnums[0]; bool seen = false; for (int j = 0; j < numfks; j++) @@ -10469,6 +10477,14 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, seen = true; break; } + if (fkperiod_attnum == setcol_attnum) + { + char *col = strVal(list_nth(fksetcols, i)); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("column \"%s\" referenced in ON DELETE SET action cannot be PERIOD ", col))); + } } if (!seen) @@ -11502,7 +11518,7 @@ FindFKComparisonOperators(Constraint *fkconstraint, * For the non-overlaps parts, we want either RTEqualStrategyNumber (without btree_gist) * or BTEqualStrategyNumber (with btree_gist). We'll try the latter first. */ - eqstrategy = for_overlaps ? RTOverlapStrategyNumber : BTEqualStrategyNumber; + eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber; } else { @@ -11521,19 +11537,11 @@ FindFKComparisonOperators(Constraint *fkconstraint, /* * There had better be a primary equality operator for the index. * We'll use it for PK = PK comparisons. + * This apply to gist and btree index method. */ ppeqop = get_opfamily_member(opfamily, opcintype, opcintype, eqstrategy); - /* Fall back to RTEqualStrategyNumber for temporal overlaps */ - if (is_temporal && !for_overlaps && !OidIsValid(ppeqop)) - { - eqstrategy = RTEqualStrategyNumber; - ppeqop = get_opfamily_member(opfamily, opcintype, opcintype, - eqstrategy); - } - - if (!OidIsValid(ppeqop)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", eqstrategy, opcintype, opcintype, opfamily); diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index d0027918..08b46536 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -245,7 +245,7 @@ static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, int queryno, bool partgone) pg_attribute_noreturn(); static bool fpo_targets_pk_range(const ForPortionOfState *tg_temporal, const RI_ConstraintInfo *riinfo); -static Datum restrict_cascading_range(const ForPortionOfState *tg_temporal, +static Datum get_portion_intersect_range(const ForPortionOfState *tg_temporal, const RI_ConstraintInfo *riinfo, TupleTableSlot *oldslot); @@ -816,7 +816,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) if (trigdata->tg_temporal) { targetRangeParam = riinfo->nkeys - 1; - targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot); + targetRange = get_portion_intersect_range(trigdata->tg_temporal, riinfo, oldslot); } ri_PerformCheck(riinfo, &qkey, qplan, @@ -1435,7 +1435,7 @@ TRI_FKey_cascade_del(PG_FUNCTION_ARGS) * Don't delete than more than the PK's duration, * trimmed by an original FOR PORTION OF if necessary. */ - targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot); + targetRange = get_portion_intersect_range(trigdata->tg_temporal, riinfo, oldslot); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -1480,7 +1480,7 @@ TRI_FKey_cascade_del(PG_FUNCTION_ARGS) quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); - sprintf(paramname, "$%d", i + 1); + snprintf(paramname, sizeof(paramname), "$%d", i + 1); ri_GenerateQual(&querybuf, querysep, paramname, pk_type, riinfo->pf_eq_oprs[i], @@ -1596,7 +1596,7 @@ TRI_FKey_cascade_upd(PG_FUNCTION_ARGS) * Don't delete than more than the PK's duration, * trimmed by an original FOR PORTION OF if necessary. */ - targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot); + targetRange = get_portion_intersect_range(trigdata->tg_temporal, riinfo, oldslot); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -1621,9 +1621,11 @@ TRI_FKey_cascade_upd(PG_FUNCTION_ARGS) * UPDATE [ONLY] <fktable> * FOR PORTION OF $fkatt FROM lower(${2n+1}) TO upper(${2n+1}) * SET fkatt1 = $1, [, ...] - * WHERE $n = fkatt1 [AND ...] + * WHERE $n + 1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Note that we are assuming + * corresponding PK attributes. ri_PerformCheck need fillin + * oldslot and newslot key values. so we put targetRange(Portion) + * to ${2n+1}. Note that we are assuming * there is an assignment cast from the PK to the FK type; * else the parser will fail. * ---------- @@ -1658,7 +1660,7 @@ TRI_FKey_cascade_upd(PG_FUNCTION_ARGS) "%s %s = $%d", querysep, attname, i + 1); - sprintf(paramname, "$%d", j + 1); + snprintf(paramname, sizeof(paramname), "$%d", j + 1); ri_GenerateQual(&qualbuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], @@ -1793,7 +1795,7 @@ tri_set(TriggerData *trigdata, bool is_set_null, int tgkind) * Don't SET NULL/DEFAULT than more than the PK's duration, * trimmed by an original FOR PORTION OF if necessary. */ - targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot); + targetRange = get_portion_intersect_range(trigdata->tg_temporal, riinfo, oldslot); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -1910,7 +1912,7 @@ tri_set(TriggerData *trigdata, bool is_set_null, int tgkind) quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); - sprintf(paramname, "$%d", i + 1); + snprintf(paramname, sizeof(paramname), "$%d", i + 1); ri_GenerateQual(&querybuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], @@ -3877,17 +3879,17 @@ fpo_targets_pk_range(const ForPortionOfState *tg_temporal, const RI_ConstraintIn } /* - * restrict_cascading_range - + * get_portion_intersect_range - * * Returns a Datum of RangeTypeP holding the appropriate timespan * to target child records when we CASCADE/SET NULL/SET DEFAULT. * - * In a normal UPDATE/DELETE this should be the parent's own valid time, - * but if there was a FOR PORTION OF clause, then we should use that to - * trim down the parent's span further. + * In a normal UPDATE/DELETE this should be the parent's own valid range, + * but if there was a FOR PORTION OF clause, then we should + * get the intersect of old valid range and FOR PORTION OF clause's range. */ static Datum -restrict_cascading_range(const ForPortionOfState *tg_temporal, const RI_ConstraintInfo *riinfo, TupleTableSlot *oldslot) +get_portion_intersect_range(const ForPortionOfState *tg_temporal, const RI_ConstraintInfo *riinfo, TupleTableSlot *oldslot) { Datum pkRecordRange; bool isnull; -- 2.34.1