Re: [HACKERS] [PATCH] WIP Add ALWAYS DEFERRED option for constraints - Mailing list pgsql-hackers

From Nico Williams
Subject Re: [HACKERS] [PATCH] WIP Add ALWAYS DEFERRED option for constraints
Date
Msg-id 20180711184110.GA9712@localhost
Whole thread Raw
In response to Re: [HACKERS] [PATCH] WIP Add ALWAYS DEFERRED option for constraints  (Robbie Harwood <rharwood@redhat.com>)
Responses Re: [HACKERS] [PATCH] WIP Add ALWAYS DEFERRED option for constraints
List pgsql-hackers
On Tue, Jun 26, 2018 at 04:54:13PM -0400, Robbie Harwood wrote:
> Nico Williams <nico@cryptonector.com> writes:
> 
> > [Re-send; first attempt appears to have hit /dev/null somewhere.  My
> > apologies if you get two copies.]
> >
> > I've finally gotten around to rebasing this patch and making the change
> > that was requested, which was: merge the now-would-be-three deferral-
> > related bool columns in various pg_catalog tables into one char column.
> >
> > Instead of (deferrable, initdeferred, alwaysdeferred), now there is just
> > (deferral).
> 
> This design seems correct to me.  I have a couple questions inline, and
> some nits to go with them.

Thanks.  Replies below.

> > All tests (make check) pass.
> 
> Thank you for adding tests!

Well, yeah :)

> > diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
> > index 3ed9021..e82e39b 100644
> > --- a/doc/src/sgml/catalogs.sgml
> > +++ b/doc/src/sgml/catalogs.sgml
> > @@ -2239,17 +2239,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
> >       </row>
> >  
> >       <row>
> > -      <entry><structfield>condeferrable</structfield></entry>
> > -      <entry><type>bool</type></entry>
> > -      <entry></entry>
> > -      <entry>Is the constraint deferrable?</entry>
> > -     </row>
> > -
> > -     <row>
> > -      <entry><structfield>condeferred</structfield></entry>
> > -      <entry><type>bool</type></entry>
> > +      <entry><structfield>condeferral</structfield></entry>
> > +      <entry><type>char</type></entry>
> >        <entry></entry>
> > -      <entry>Is the constraint deferred by default?</entry>
> > +      <entry>Constraint deferral option:
> > +            <literal>a</literal> = always deferred,
> > +            <literal>d</literal> = deferrable,
> > +            <literal>d</literal> = deferrable initially deferred,
> 
> From the rest of the code, I think this is supposed to be 'i', not 'd'.

Oh yes, good catch.

> > +            <literal>n</literal> = not deferrable
> > +          </entry>
> >       </row>
> >  
> >       <row>
> 
> > diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
> > index 8b276bc..795a7a9 100644
> > --- a/src/backend/catalog/index.c
> > +++ b/src/backend/catalog/index.c
> > @@ -1070,6 +1070,7 @@ index_create(Relation heapRelation,
> >  
> >                  recordDependencyOn(&myself, &referenced, deptype);
> >              }
> > +            Assert((flags & INDEX_CREATE_ADD_CONSTRAINT) == 0);
> 
> What does this ensure, and why is it in this part of the code?  We're in
> the `else` branch here - isn't this always the case (i.e., Assert can
> never fire), since `flags` isn't manipulated in this block?

Oy, nothing.  I think that's a cut-n-paste error.

I'll remove it.

> >          }
> >  
> >          /* Store dependency on parent index, if any */
> 
> > diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
> > index f4e69f4..bde6199 100644
> > --- a/src/backend/catalog/information_schema.sql
> > +++ b/src/backend/catalog/information_schema.sql
> > @@ -891,10 +891,14 @@ CREATE VIEW domain_constraints AS
> >             CAST(current_database() AS sql_identifier) AS domain_catalog,
> >             CAST(n.nspname AS sql_identifier) AS domain_schema,
> >             CAST(t.typname AS sql_identifier) AS domain_name,
> > -           CAST(CASE WHEN condeferrable THEN 'YES' ELSE 'NO' END
> > +           CAST(CASE WHEN condeferral = 'n' THEN 'NO' ELSE 'YES' END
> >               AS yes_or_no) AS is_deferrable,
> > -           CAST(CASE WHEN condeferred THEN 'YES' ELSE 'NO' END
> > +           CAST(CASE WHEN condeferral = 'i' OR condeferral = 'a' THEN 'YES' ELSE 'NO' END
> >               AS yes_or_no) AS initially_deferred
> > +       /*
> > +        * XXX Can we add is_always_deferred here?  Are there
> > +        * standards considerations?
> > +        */
> 
> I'm not familiar enough to speak to this.  Hopefully someone else can.
> Absent other input, I think we should add is_always_deferred.  (And if
> we were building this today, probably just expose a single character
> instead of three booleans.)

I had found the answer ("yes") in the history of this file but forgot to
remove the comment while rebasing.  I'll remove the comment.

> >      FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t
> >      WHERE rs.oid = con.connamespace
> >            AND n.oid = t.typnamespace
> 
> > diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
> > index 57519fe..41dc6a4 100644
> > --- a/src/backend/commands/trigger.c
> > +++ b/src/backend/commands/trigger.c
> > @@ -3632,6 +3627,7 @@ typedef struct AfterTriggerSharedData
> >      TriggerEvent ats_event;        /* event type indicator, see trigger.h */
> >      Oid            ats_tgoid;        /* the trigger's ID */
> >      Oid            ats_relid;        /* the relation it's on */
> > +    bool            ats_alwaysdeferred;    /* whether this can be deferred */
> 
> "Whether this must be deferred"?  Also, I'm not sure what this is for -
> it doesn't seem to be used anywhere.

Ah, this became unused during the rebase.  I'll remove it.

> >      CommandId    ats_firing_id;    /* ID for firing cycle */
> >      struct AfterTriggersTableData *ats_table;    /* transition table access */
> >  } AfterTriggerSharedData;
> 
> > diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
> > index 90dfac2..dab721a 100644
> > --- a/src/backend/parser/gram.y
> > +++ b/src/backend/parser/gram.y
> > @@ -184,8 +185,8 @@ static void SplitColQualList(List *qualList,
> >                               List **constraintList, CollateClause **collClause,
> >                               core_yyscan_t yyscanner);
> >  static void processCASbits(int cas_bits, int location, const char *constrType,
> > -               bool *deferrable, bool *initdeferred, bool *not_valid,
> > -               bool *no_inherit, core_yyscan_t yyscanner);
> > +               char *deferral,
> > +               bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner);
> 
> Can you fix the wrapping on this?

Maybe!  :)  I couldn't quite figure out what it should be.  There
doesn't seem to be a consistent style of wrapping.

> >  static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
> >  
> >  %}
> > @@ -5538,17 +5568,24 @@ ConstraintAttributeSpec:
> >                      int        newspec = $1 | $2;
> >  
> >                      /* special message for this case */
> > -                    if ((newspec & (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED)) == (CAS_NOT_DEFERRABLE |
CAS_INITIALLY_DEFERRED))
> > +                    if ((newspec & CAS_NOT_DEFERRABLE) &&
> > +                        (newspec & (CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED)))
> >                          ereport(ERROR,
> >                                  (errcode(ERRCODE_SYNTAX_ERROR),
> >                                   errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
> >                                   parser_errposition(@2)));
> >                      /* generic message for other conflicts */
> > +                    if ((newspec & CAS_ALWAYS_DEFERRED) &&
> > +                        (newspec & (CAS_INITIALLY_IMMEDIATE)))
> > +                        ereport(ERROR,
> > +                                (errcode(ERRCODE_SYNTAX_ERROR),
> > +                                 errmsg("conflicting constraint properties 1"),
> > +                                 parser_errposition(@2)));
> >                      if ((newspec & (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE)) == (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE)
||
> >                          (newspec & (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED)) == (CAS_INITIALLY_IMMEDIATE
|CAS_INITIALLY_DEFERRED))
 
> >                          ereport(ERROR,
> >                                  (errcode(ERRCODE_SYNTAX_ERROR),
> > -                                 errmsg("conflicting constraint properties"),
> > +                                 errmsg("conflicting constraint properties 2"),
> 
> I'd prefer you just repeat the message (or make them more situationally
> descriptive), rather than appending a number.  (Repeating error messages
> is in keeping with the style here.)

Oy, I forgot about these.  The number was a bread crumb I forgot to
cleanup :(  So sorry about that.

> >                                   parser_errposition(@2)));
> >                      $$ = newspec;
> >                  }
> > @@ -16197,34 +16234,41 @@ SplitColQualList(List *qualList,
> >   */
> >  static void
> >  processCASbits(int cas_bits, int location, const char *constrType,
> > -               bool *deferrable, bool *initdeferred, bool *not_valid,
> > -               bool *no_inherit, core_yyscan_t yyscanner)
> > +               char *deferral,
> > +               bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner)
> 
> Line wrapping?
> 
> >  {
> >      /* defaults */
> > -    if (deferrable)
> > -        *deferrable = false;
> > -    if (initdeferred)
> > -        *initdeferred = false;
> > +    if (deferral)
> > +        *deferral = 'n';
> >      if (not_valid)
> >          *not_valid = false;
> >  
> > -    if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED))
> > +    if (cas_bits & CAS_ALWAYS_DEFERRED)
> >      {
> > -        if (deferrable)
> > -            *deferrable = true;
> > +        if (deferral)
> > +            *deferral = 'a';
> >          else
> >              ereport(ERROR,
> >                      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> >                       /* translator: %s is CHECK, UNIQUE, or similar */
> > -                     errmsg("%s constraints cannot be marked DEFERRABLE",
> > +                     errmsg("%s constraints cannot be marked ALWAYS DEFERRED",
> >                              constrType),
> >                       parser_errposition(location)));
> > -    }
> > -
> > -    if (cas_bits & CAS_INITIALLY_DEFERRED)
> > +    } else if (cas_bits & CAS_INITIALLY_DEFERRED)
> 
> Style on this file doesn't cuddle `else`.  (i.e., `else if (cond)` gets its own
> line without any braces on it.)

OK.

> > +    {
> > +        if (deferral)
> > +            *deferral = 'i';
> > +        else
> > +            ereport(ERROR,
> > +                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> > +                     /* translator: %s is CHECK, UNIQUE, or similar */
> > +                     errmsg("%s constraints cannot be marked INITIALLY DEFERRED",
> > +                            constrType),
> > +                     parser_errposition(location)));
> > +    } else if (cas_bits & CAS_DEFERRABLE)
> >      {
> > -        if (initdeferred)
> > -            *initdeferred = true;
> > +        if (deferral)
> > +            *deferral = 'd';
> >          else
> >              ereport(ERROR,
> >                      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> 
> Thanks,
> --Robbie

Thank you.  I'll rebase, update, and post a new patch soon.

Nico
-- 


pgsql-hackers by date:

Previous
From: Asim R P
Date:
Subject: Re: Shared buffer access rule violations?
Next
From: Andres Freund
Date:
Subject: Shouldn't validateForeignKeyConstraint() reset memory context?