Re: Fwd: Problem with a "complex" upsert - Mailing list pgsql-bugs

From Andres Freund
Subject Re: Fwd: Problem with a "complex" upsert
Date
Msg-id 20180803235149.bdgks37i3u5avziz@alap3.anarazel.de
Whole thread Raw
In response to Re: Fwd: Problem with a "complex" upsert  (Dean Rasheed <dean.a.rasheed@gmail.com>)
Responses Re: Fwd: Problem with a "complex" upsert  (Peter Geoghegan <pg@bowt.ie>)
List pgsql-bugs
Hi,

On 2018-08-03 10:40:50 +0100, Dean Rasheed wrote:
> On 3 August 2018 at 07:52, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
> > This doesn't look right to me. It breaks the following case ...
> 
> Here's an updated patch that fixes this.

Looking through the patch.


> diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
> new file mode 100644
> index 05f5759..87e084b
> --- a/src/backend/parser/analyze.c
> +++ b/src/backend/parser/analyze.c
> @@ -1022,9 +1022,6 @@ transformOnConflictClause(ParseState *ps
>      if (onConflictClause->action == ONCONFLICT_UPDATE)
>      {
>          Relation    targetrel = pstate->p_target_relation;
> -        Var           *var;
> -        TargetEntry *te;
> -        int            attno;
>  
>          /*
>           * All INSERT expressions have been parsed, get ready for potentially
> @@ -1043,56 +1040,8 @@ transformOnConflictClause(ParseState *ps
>                                                  false, false);
>          exclRte->relkind = RELKIND_COMPOSITE_TYPE;
>          exclRelIndex = list_length(pstate->p_rtable);
> -
> -        /*
> -         * Build a targetlist representing the columns of the EXCLUDED pseudo
> -         * relation.  Have to be careful to use resnos that correspond to
> -         * attnos of the underlying relation.
> -         */
> -        for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
> -        {
> -            Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
> -            char       *name;
> -
> -            if (attr->attisdropped)
> -            {
> -                /*
> -                 * can't use atttypid here, but it doesn't really matter what
> -                 * type the Const claims to be.
> -                 */
> -                var = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
> -                name = "";
> -            }
> -            else
> -            {
> -                var = makeVar(exclRelIndex, attno + 1,
> -                              attr->atttypid, attr->atttypmod,
> -                              attr->attcollation,
> -                              0);
> -                name = pstrdup(NameStr(attr->attname));
> -            }
> -
> -            te = makeTargetEntry((Expr *) var,
> -                                 attno + 1,
> -                                 name,
> -                                 false);
> -
> -            /* don't require select access yet */
> -            exclRelTlist = lappend(exclRelTlist, te);
> -        }
> -
> -        /*
> -         * Add a whole-row-Var entry to support references to "EXCLUDED.*".
> -         * Like the other entries in exclRelTlist, its resno must match the
> -         * Var's varattno, else the wrong things happen while resolving
> -         * references in setrefs.c.  This is against normal conventions for
> -         * targetlists, but it's okay since we don't use this as a real tlist.
> -         */
> -        var = makeVar(exclRelIndex, InvalidAttrNumber,
> -                      targetrel->rd_rel->reltype,
> -                      -1, InvalidOid, 0);
> -        te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true);
> -        exclRelTlist = lappend(exclRelTlist, te);
> +        exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
> +                                                         exclRelIndex);
>  
>          /*
>           * Add EXCLUDED and the target RTE to the namespace, so that they can
> @@ -1124,6 +1073,75 @@ transformOnConflictClause(ParseState *ps
>  
>      return result;
>  }
> +
> +
> +/*
> + * BuildOnConflictExcludedTargetlist
> + *        Create the target list of EXCLUDED pseudo-relation of ON CONFLICT 
> + *
> + * Note: Exported for use in the rewriter.
> + */
> +List *
> +BuildOnConflictExcludedTargetlist(Relation targetrel,
> +                                  Index exclRelIndex)
> +{
> +    List       *result = NIL;
> +    int            attno;
> +    Var           *var;
> +    TargetEntry *te;
> +
> +    /*
> +     * Build a targetlist representing the columns of the EXCLUDED pseudo
> +     * relation.  Have to be careful to use resnos that correspond to attnos
> +     * of the underlying relation.
> +     */
> +    for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
> +    {
> +        Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
> +        char       *name;
> +
> +        if (attr->attisdropped)
> +        {
> +            /*
> +             * can't use atttypid here, but it doesn't really matter what type
> +             * the Const claims to be.
> +             */
> +            var = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
> +            name = "";
> +        }
> +        else
> +        {
> +            var = makeVar(exclRelIndex, attno + 1,
> +                          attr->atttypid, attr->atttypmod,
> +                          attr->attcollation,
> +                          0);
> +            name = pstrdup(NameStr(attr->attname));
> +        }
> +
> +        te = makeTargetEntry((Expr *) var,
> +                             attno + 1,
> +                             name,
> +                             false);
> +
> +        /* don't require select access yet */
> +        result = lappend(result, te);
> +    }
> +
> +    /*
> +     * Add a whole-row-Var entry to support references to "EXCLUDED.*". Like
> +     * the other entries in exclRelTlist, its resno must match the Var's
> +     * varattno, else the wrong things happen while resolving references in
> +     * setrefs.c.  This is against normal conventions for targetlists, but
> +     * it's okay since we don't use this as a real tlist.
> +     */
> +    var = makeVar(exclRelIndex, InvalidAttrNumber,
> +                  targetrel->rd_rel->reltype,
> +                  -1, InvalidOid, 0);
> +    te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true);
> +    result = lappend(result, te);
> +
> +    return result;
> +}

On a skim this purely is moving code around - no functional changes, right?


> +-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
> +-- columns are named and ordered differently than the underlying table's.
> +create table uv_iocu_tab (a text unique, b float);
> +insert into uv_iocu_tab values ('xyxyxy', 1);
> +create view uv_iocu_view as select b, b+1 as c, a from uv_iocu_tab;
> +insert into uv_iocu_view (a, b) values ('xyxyxy', 2)
> +   on conflict (a) do update set b = uv_iocu_view.b;
> +
> +-- OK to access view columns that are not present in underlying base
> +-- relation in the ON CONFLICT portion of the query
> +explain (costs off)
> +insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
> +   on conflict (a) do update set b = excluded.b where excluded.c > 0;
> +
> +insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
> +   on conflict (a) do update set b = excluded.b where excluded.c > 0;
> +-- should display 'xyxyxy, 3'
> +select * from uv_iocu_tab;
> +drop view uv_iocu_view;
> +drop table uv_iocu_tab;
> +
> +-- Example with whole-row references to the view
> +create table uv_iocu_tab (a int unique, b text);
> +create view uv_iocu_view as
> +    select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab;
> +
> +insert into uv_iocu_view (aa,bb) values (1,'x');
> +explain (costs off)
> +insert into uv_iocu_view (aa,bb) values (1,'y')
> +   on conflict (aa) do update set bb = 'Rejected: '||excluded.*
> +   where excluded.aa > 0
> +   and excluded.bb != ''
> +   and excluded.cc is not null;
> +insert into uv_iocu_view (aa,bb) values (1,'y')
> +   on conflict (aa) do update set bb = 'Rejected: '||excluded.*
> +   where excluded.aa > 0
> +   and excluded.bb != ''
> +   and excluded.cc is not null;
> +select * from uv_iocu_view;
> +
> +-- Test omiting a column of the base relation
> +delete from uv_iocu_view;
> +insert into uv_iocu_view (aa,bb) values (1,'x');
> +insert into uv_iocu_view (aa) values (1)
> +   on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
> +select * from uv_iocu_view;
> +
> +-- Should fail to update non-updatable columns
> +insert into uv_iocu_view (aa) values (1)
> +   on conflict (aa) do update set cc = 'XXX';
> +
> +drop view uv_iocu_view;
> +drop table uv_iocu_tab;

Could you add a column that's just a const and one that that's now() or
something? And based on those add a test that makes sure we don't do
stupid thinks when they're referred to via EXCLUDED?  AFAICS that
currently should work correctly, but it'd be good to test that.


Peter, I think, independent of this bug, I think it'd be good to add a
few tests around arbiter expression for ON CONFLICT over a view.

Greetings,

Andres Freund


pgsql-bugs by date:

Previous
From: Peter Geoghegan
Date:
Subject: Re: Fwd: Problem with a "complex" upsert
Next
From: Peter Geoghegan
Date:
Subject: Re: Fwd: Problem with a "complex" upsert