Re: Allow foreign keys to reference a superset of unique columns - Mailing list pgsql-hackers

From Kaiting Chen
Subject Re: Allow foreign keys to reference a superset of unique columns
Date
Msg-id CA+CLzG-mkhPh70BM5FiyH=VFA=4tLN4CAgbgC+_0Pu6L9PSU4Q@mail.gmail.com
Whole thread Raw
In response to Re: Allow foreign keys to reference a superset of unique columns  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
> For one example of where the semantics get fuzzy, it's not
> very clear how the extra-baggage columns ought to participate in
> CASCADE updates.  Currently, if we have
>    CREATE TABLE foo (a integer PRIMARY KEY, b integer);
> then an update that changes only foo.b doesn't need to update
> referencing tables, and I think we even have optimizations that
> assume that if no unique-key columns are touched then RI checks
> need not be made.

Regarding optimizations that skip RI checks on the PK side of the relationship,
I believe the relevant code is here (in trigger.c):

  if (TRIGGER_FIRED_BY_UPDATE(event) || TRIGGER_FIRED_BY_DELETE(event)) {
      ...

      switch (RI_FKey_trigger_type(trigger->tgfoid)) {
          ...

          case RI_TRIGGER_PK:
              ...

              /* Update or delete on trigger's PK table */
              if (!RI_FKey_pk_upd_check_required(trigger, rel, oldslot, newslot))
              {
                  /* skip queuing this event */
                  continue;
              }

              ...

And the checks done in RI_FKey_pk_upd_check_required() are:

  const RI_ConstraintInfo *riinfo;

  riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true);

  /*
    * If any old key value is NULL, the row could not have been referenced by
    * an FK row, so no check is needed.
    */
  if (ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) != RI_KEYS_NONE_NULL)
          return false;

  /* If all old and new key values are equal, no check is needed */
  if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
          return false;

  /* Else we need to fire the trigger. */
  return true;

The columns inspected by both ri_NullCheck() and ri_KeysEqual() are based on the
riinfo->pk_attnums:

  if (rel_is_pk)
          attnums = riinfo->pk_attnums;
  else
          attnums = riinfo->fk_attnums;

The check in ri_NullCheck() is, expectedly, a straightforward NULL check:

  for (int i = 0; i < riinfo->nkeys; i++)
  {
          if (slot_attisnull(slot, attnums[i]))
                  nonenull = false;
          else
                  allnull = false;
  }

The check in ri_KeysEqual() is a bytewise comparison:

  /* XXX: could be worthwhile to fetch all necessary attrs at once */
  for (int i = 0; i < riinfo->nkeys; i++)
  {
          Datum oldvalue;
          Datum newvalue;
          bool isnull;

          /*
            * Get one attribute's oldvalue. If it is NULL - they're not equal.
            */
          oldvalue = slot_getattr(oldslot, attnums[i], &isnull);
          if (isnull)
                  return false;

          /*
            * Get one attribute's newvalue. If it is NULL - they're not equal.
            */
          newvalue = slot_getattr(newslot, attnums[i], &isnull);
          if (isnull)
                  return false;

          if (rel_is_pk)
          {
                  /*
                    * If we are looking at the PK table, then do a bytewise
                    * comparison.  We must propagate PK changes if the value is
                    * changed to one that "looks" different but would compare as
                    * equal using the equality operator.  This only makes a
                    * difference for ON UPDATE CASCADE, but for consistency we treat
                    * all changes to the PK the same.
                    */
                  Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);

                  if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
                          return false;
          }
          else
          {
                  /*
                    * For the FK table, compare with the appropriate equality
                    * operator.  Changes that compare equal will still satisfy the
                    * constraint after the update.
                    */
                  if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
                                                                  oldvalue, newvalue))
                          return false;
          }
  }

It seems like neither optimization actually requires the presence of the unique
index. And, as my proposed patch would leave both riinfo->nkeys and
riinfo->pk_attnums exactly the same as before, I don't believe that it should
have any impact on these optimizations.

pgsql-hackers by date:

Previous
From: Zhang Mingli
Date:
Subject: Re: Summary function for pg_buffercache
Next
From: Robert Haas
Date:
Subject: Re: Avoid memory leaks during base backups