Re: insert multiple rows attempt two - Mailing list pgsql-patches

From Tom Lane
Subject Re: insert multiple rows attempt two
Date
Msg-id 6145.998589751@sss.pgh.pa.us
Whole thread Raw
In response to insert multiple rows attempt two  (Liam Stewart <liams@redhat.com>)
List pgsql-patches
Liam Stewart <liams@redhat.com> writes:
> Here is a second attempt at a patch for inserting multiple values with
> an INSERT ... VALUES. I did made some simple regression tests while
> working on this patch so I'm sending them along as well.
> Comments?

Getting there, but still needs work.

> *** src/backend/executor/nodeResult.c    2001/03/22 06:16:13    1.19
> --- src/backend/executor/nodeResult.c    2001/08/22 14:55:30
> ***************
> *** 148,157 ****
>           {

>               /*
> !              * if we don't have an outer plan, then we are just generating
> !              * the results from a constant target list.  Do it only once.
>                */
> !             resstate->rs_done = true;
>           }

>           /*
> --- 148,168 ----
>           {

>               /*
> !              * if we don't have an outer plan, then we are just
> !              * generating the results from a constant list of target
> !              * lists. Do it only while the list has elements in it.
> !              * An item in the list is placed in the pi_targetlist of
> !              * resstate->cstate.cs_ProjInfo for processing.
>                */
> !             if (node->valueslist == NIL)
> !                 resstate->rs_done = true;
> !             else
> !             {
> !                 if (length(node->valueslist) == 1)
> !                     resstate->rs_done = true;
> !                 resstate->cstate.cs_ProjInfo->pi_targetlist = lfirst(node->valueslist);
> !                 node->valueslist = lnext(node->valueslist);
> !             }
>           }

>           /*

This is not okay: you're physically destroying the contents of the
Result node, which means that the plan cannot be rerun.  In particular,
any attempt to rescan it will give wrong results.  (I'm not sure that
rescan can happen during a simple INSERT ... VALUES command, but there's
no point in having a Result-node feature that's broken in the general
case.)

What you need, I think, is a counter or pointer in the ResultState node
that points at the current targetlists-list element, and is initialized
(reset) by ExecInitResult and ExecReScanResult.  The Result node
structure needs to be treated as read-only here.

I'd also prefer not to see schizophrenia over whether Result pays
attention to a targetlist or a list-of-targetlists; IMHO there should be
only one code path dealing with the list.  Places that currently set up
Results with a single targetlist should have a makeList1() added to convert
their targetlist to a list of lists.

> !             if (qry->valuesList == NIL)
> !                 qry->valuesList = makeList1(transformTargetList(pstate, tl));
> !             else
> !                 lappend(qry->valuesList, transformTargetList(pstate, tl));

I see a number of places where you did list extension like that.  While
it works, it's verbose and not idiomatic.  Correct procedure is just

    qry->valuesList = lappend(qry->valuesList,
                  transformTargetList(pstate, tl));

There's no need for a special case for a currently-nil list this way.

> *** src/include/nodes/parsenodes.h    2001/08/21 16:36:06    1.142
> --- src/include/nodes/parsenodes.h    2001/08/22 14:55:33
> ***************
> *** 57,62 ****
> --- 57,65 ----

>       List       *targetList;        /* target list (of TargetEntry) */

> +     List       *valuesList;        /* list of lists of TargetEntries
> +                                  * (VALUES clauses) */
> +
>       List       *groupClause;    /* a list of GroupClause's */

I do not like the fact that you have added a field to Query that has
such a poorly-defined relation to targetList.  Which is primary?
Can it possibly be correct that there is only one place in the planner
and rewriter that needs to look at valuesList as well as targetList?
(I doubt it.)  Can it possibly be correct that targetList may point at
substructure of valueList?  (Definitely not; that will lead to two
passes of processing over the same node tree, which is not good.  One
case that'll almost certainly misbehave is where there are sub-SELECTs
in the targetlist/valuelist.)

The only way to do this that would give me any confidence in the code
at all is to *not* add a separate valuesList field, but to replace
targetList by a targetLists field that's defined as a list of lists of
TargetEntries.  Renaming the field guarantees that everyplace that
currently touches targetList breaks, and as you pass through the code
fixing that, you can fix anyplace that assumes it's dealing with a
single-level list of TargetEntries.  For some places it doesn't matter
(example: the node-copying code still just invokes Node_Copy); for some
you will need to replace a one-level loop with a two-level loop; and
for some it may be appropriate to assume there is only one list of
target entries and just insert an lfirst() to get to it.  (I'd suggest
adding Assert(length(targetLists) == 1) to places where you assume
this.)

>       /*
> !      * An INSERT statement has *either* VALUES or SELECT, never both. If
> !      * VALUES, a targetList is supplied (empty for DEFAULT VALUES). If
> !      * SELECT, a complete SelectStmt (or set-operation tree) is supplied.
>        */
> !     List       *targetList;        /* the target list (of ResTarget) */
>       Node       *selectStmt;        /* the source SELECT */
>   } InsertStmt;

> --- 808,819 ----
>       List       *cols;            /* optional: names of the target columns */

>       /*
> !      * An INSERT statement has *either* VALUES or SELECT, never
> !      * both. If VALUES, a list of lists of ResTarget's is supplied
> !      * (empty for DEFAULT VALUES). If SELECT, a complete SelectStmt
> !      * (or set-operation tree) is supplied.
>        */
> !     List       *values;            /* the values to insert */
>       Node       *selectStmt;        /* the source SELECT */
>   } InsertStmt;

See, you did InsertStmt cleanly.  But Query has to have the same kind of
fundamental alteration.

            regards, tom lane

pgsql-patches by date:

Previous
From: Vsevolod Lobko
Date:
Subject: Re: Patch for pl/tcl Tcl_ExternalToUtf and Tcl_UtfToExternal support
Next
From: Bruce Momjian
Date:
Subject: Re: Patch for pl/tcl Tcl_ExternalToUtf and Tcl_UtfToExternal support