Thread: Changed SRF in targetlist handling

Changed SRF in targetlist handling

From
Andres Freund
Date:
Hi,

discussing executor performance with a number of people at pgcon,
several hackers - me included - complained about the additional
complexity, both code and runtime, required to handle SRFs in the target
list.

One idea I circulated was to fix that by interjecting a special executor
node to process SRF containing targetlists (reusing Result possibly?).
That'd allow to remove the isDone argument from ExecEval*/ExecProject*
and get rid of ps_TupFromTlist which is fairly ugly.


Robert suggested - IIRC mentioning previous on-list discussion - to
instead rewrite targetlist SRFs into lateral joins. My gut feeling is
that that'd be a larger undertaking, with significant semantics changes.

If we accept bigger semantical changes, I'm inclined to instead just get
rid of targetlist SRFs in total; they're really weird and not needed
anymore.

One issue with removing targetlist SRFs is that they're currently
considerably faster than SRFs in FROM:
tpch[14693][1]=# COPY (SELECT * FROM generate_series(1, 10000000)) TO '/dev/null';
COPY 10000000
Time: 2217.167 ms
tpch[14693][1]=# COPY (SELECT generate_series(1, 10000000)) TO '/dev/null';
COPY 10000000
Time: 1355.929 ms
tpch[14693][1]=#

I'm no tto concerned about that, and we could probably fixing by
removing forced materialization from the relevant code path.

Comments?

Greetings,

Andres Freund



Re: Changed SRF in targetlist handling

From
Craig Ringer
Date:
On 23 May 2016 at 08:53, Andres Freund <andres@anarazel.de> wrote:
Hi,

discussing executor performance with a number of people at pgcon,
several hackers - me included - complained about the additional
complexity, both code and runtime, required to handle SRFs in the target
list.

One idea I circulated was to fix that by interjecting a special executor
node to process SRF containing targetlists (reusing Result possibly?).
That'd allow to remove the isDone argument from ExecEval*/ExecProject*
and get rid of ps_TupFromTlist which is fairly ugly.


Robert suggested - IIRC mentioning previous on-list discussion - to
instead rewrite targetlist SRFs into lateral joins. My gut feeling is
that that'd be a larger undertaking, with significant semantics changes.

If we accept bigger semantical changes, I'm inclined to instead just get
rid of targetlist SRFs in total; they're really weird and not needed
anymore.

One issue with removing targetlist SRFs is that they're currently
considerably faster than SRFs in FROM:
tpch[14693][1]=# COPY (SELECT * FROM generate_series(1, 10000000)) TO '/dev/null';
COPY 10000000
Time: 2217.167 ms
tpch[14693][1]=# COPY (SELECT generate_series(1, 10000000)) TO '/dev/null';
COPY 10000000
Time: 1355.929 ms
tpch[14693][1]=#

I'm no tto concerned about that, and we could probably fixing by
removing forced materialization from the relevant code path.

Comments?


SRFs-in-tlist are a lot faster for lockstep iteration etc. They're also much simpler to write, though if the result result rowcount differs unexpectedly between the functions you get exciting and unexpected behaviour.

WITH ORDINALITY provides what I think is the last of the functionality needed to replace SRFs-in-from, but at a syntatactic complexity and performance cost. The following example demonstrates that, though it doesn't do anything that needs LATERAL etc. I'm aware the following aren't semantically identical if the rowcounts differ.

 
craig=> EXPLAIN ANALYZE SELECT generate_series(1,1000000) x, generate_series(1,1000000) y;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 Result  (cost=0.00..5.01 rows=1000 width=0) (actual time=0.024..92.845 rows=1000000 loops=1)
 Planning time: 0.039 ms
 Execution time: 123.123 ms
(3 rows)

Time: 123.719 ms


craig=> EXPLAIN ANALYZE SELECT x, y FROM generate_series(1,1000000) WITH ORDINALITY AS x(i, n) INNER JOIN generate_series(1,1000000) WITH ORDINALITY AS y(i, n) ON (x.n = y.n);
                                                                QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
 Merge Join  (cost=0.01..97.50 rows=5000 width=64) (actual time=179.863..938.375 rows=1000000 loops=1)
   Merge Cond: (x.n = y.n)
   ->  Function Scan on generate_series x  (cost=0.00..10.00 rows=1000 width=40) (actual time=108.813..303.690 rows=1000000 loops=1)
   ->  Materialize  (cost=0.00..12.50 rows=1000 width=40) (actual time=71.043..372.880 rows=1000000 loops=1)
         ->  Function Scan on generate_series y  (cost=0.00..10.00 rows=1000 width=40) (actual time=71.039..266.209 rows=1000000 loops=1)
 Planning time: 0.184 ms
 Execution time: 970.744 ms
(7 rows)

Time: 971.706 ms


I get the impression the with-ordinality case could perform just as well if the optimiser recognised a join on the ordinality column and iterated the functions in lockstep to populate the result row directly. Though that could perform _worse_ if the function is computationally costly and benefits significantly from the CPU cache, where we're better off materializing it or at least executing it in chunks/batches...


--
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
tl;dr

Semantic changes to SRF-in-target-list processing are undesirable when they are all but deprecated.

I'd accept a refactoring that trades a performance gain for unaffected queries for a reasonable performance hit of those afflicted.

Preamble...

Most recent thread that I can recall seeing on the topic - and where I believe the rewrite idea was first presented.


On Sun, May 22, 2016 at 8:53 PM, Andres Freund <andres@anarazel.de> wrote:
Hi,

discussing executor performance with a number of people at pgcon,
several hackers - me included - complained about the additional
complexity, both code and runtime, required to handle SRFs in the target
list.

One idea I circulated was to fix that by interjecting a special executor
node to process SRF containing targetlists (reusing Result possibly?).
That'd allow to remove the isDone argument from ExecEval*/ExecProject*
and get rid of ps_TupFromTlist which is fairly ugly.

​Conceptually I'm all for minimizing the impact on queries of this form.  It seems to be the most likely to get written and committed and the least likely to cause unforeseen issues.
 
Robert suggested - IIRC mentioning previous on-list discussion - to
instead rewrite targetlist SRFs into lateral joins. My gut feeling is
that that'd be a larger undertaking, with significant semantics changes.
​[...]​
If we accept bigger semantical changes, I'm inclined to instead just get
rid of targetlist SRFs in total; they're really weird and not needed
anymore.

​I cannot see these, in isolation, being a good option.  Nonetheless, I don't think any semantic change should happen before 9.2 becomes no longer supported.  I'd be inclined to take a similar approach as with standard_conforming_strings (minus the execution guc, just the warning one) with whatever after-the-fact learning taken into account.

Its worth considering query rewrite and making it forbidden as a joint goal.

For something like a canonical version of this, especially for composite-returning SRF:

WITH func_call (
SELECT func(tbl.col)
FROM tbl
)
​SELECT (func_call.func).*
FROM func_call;​

If we can rewrite the CTE portion into a lateral - with the exact same semantics (specifically, returning the single-column composite) then check the rewritten query the select list SRF would not longer be present and no error would be thrown.

For situations where a rewrite cannot be made to behave properly we leave the construct alone and let the query raise an error.

In considering what I just wrote I'm not particularly enamored with it...hence my overall conclusion.  Can't say I hate it and after re-reading the aforementioned thread I'm inclined to like it for cases where, for instance, we are susceptible to a LCM evaluation. 

David J.

Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> discussing executor performance with a number of people at pgcon,
> several hackers - me included - complained about the additional
> complexity, both code and runtime, required to handle SRFs in the target
> list.

Yeah, this has been an annoyance for a long time.

> One idea I circulated was to fix that by interjecting a special executor
> node to process SRF containing targetlists (reusing Result possibly?).
> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
> and get rid of ps_TupFromTlist which is fairly ugly.

Would that not lead to, in effect, duplicating all of execQual.c?  The new
executor node would still have to be prepared to process all expression
node types.

> Robert suggested - IIRC mentioning previous on-list discussion - to
> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
> that that'd be a larger undertaking, with significant semantics changes.

Yes, this was discussed on-list awhile back (I see David found a reference
already).  I think it's feasible, although we'd first have to agree
whether we want to remain bug-compatible with the old
least-common-multiple-of-the-periods behavior.  I would vote for not,
but it's certainly a debatable thing.

> If we accept bigger semantical changes, I'm inclined to instead just get
> rid of targetlist SRFs in total; they're really weird and not needed
> anymore.

This seems a bridge too far to me.  It's just way too common to do
"select generate_series(1,n)".  We could tell people they have to
rewrite to "select * from generate_series(1,n)", but it would be far
more polite to do that for them.

> One issue with removing targetlist SRFs is that they're currently
> considerably faster than SRFs in FROM:

I suspect that depends greatly on your test case.  But in any case
we could put more effort into optimizing nodeFunctionscan.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
David Fetter
Date:
On Mon, May 23, 2016 at 01:10:29PM -0400, Tom Lane wrote:
> This seems a bridge too far to me.  It's just way too common to do
> "select generate_series(1,n)".  We could tell people they have to
> rewrite to "select * from generate_series(1,n)", but it would be far
> more polite to do that for them.

How about making "TABLE generate_series(1,n)" work?  It's even
shorter in exchange for some cognitive load.

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
David Fetter <david@fetter.org> writes:
> On Mon, May 23, 2016 at 01:10:29PM -0400, Tom Lane wrote:
>> This seems a bridge too far to me.  It's just way too common to do
>> "select generate_series(1,n)".  We could tell people they have to
>> rewrite to "select * from generate_series(1,n)", but it would be far
>> more polite to do that for them.

> How about making "TABLE generate_series(1,n)" work?  It's even
> shorter in exchange for some cognitive load.

No thanks --- the word after TABLE ought to be a table name, not some
arbitrary expression.  That's way too much mess to save one keystroke.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
David Fetter
Date:
On Mon, May 23, 2016 at 01:36:57PM -0400, Tom Lane wrote:
> David Fetter <david@fetter.org> writes:
> > On Mon, May 23, 2016 at 01:10:29PM -0400, Tom Lane wrote:
> >> This seems a bridge too far to me.  It's just way too common to do
> >> "select generate_series(1,n)".  We could tell people they have to
> >> rewrite to "select * from generate_series(1,n)", but it would be far
> >> more polite to do that for them.
> 
> > How about making "TABLE generate_series(1,n)" work?  It's even
> > shorter in exchange for some cognitive load.
> 
> No thanks --- the word after TABLE ought to be a table name, not some
> arbitrary expression.  That's way too much mess to save one keystroke.

It's not just about saving a keystroke.  This change would go with
removing the ability to do SRFs in the target list of a SELECT
query.

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
David Fetter <david@fetter.org> writes:
> On Mon, May 23, 2016 at 01:36:57PM -0400, Tom Lane wrote:
>> David Fetter <david@fetter.org> writes:
>>> How about making "TABLE generate_series(1,n)" work?  It's even
>>> shorter in exchange for some cognitive load.

>> No thanks --- the word after TABLE ought to be a table name, not some
>> arbitrary expression.  That's way too much mess to save one keystroke.

> It's not just about saving a keystroke.  This change would go with
> removing the ability to do SRFs in the target list of a SELECT
> query.

I guess you did not understand that I was rejecting doing that.
Telling people they have to modify existing code that does this and
works fine is exactly what I felt we can't do.  We might be able to
blow off complicated cases, but I think simpler cases are too common
in the field.

I'm on board with fixing things so that the *implementation* doesn't
support SRF-in-tlist.  But we can't just remove it from the language.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Merlin Moncure
Date:
On Mon, May 23, 2016 at 12:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> discussing executor performance with a number of people at pgcon,
>> several hackers - me included - complained about the additional
>> complexity, both code and runtime, required to handle SRFs in the target
>> list.
>
> Yeah, this has been an annoyance for a long time.
>
>> One idea I circulated was to fix that by interjecting a special executor
>> node to process SRF containing targetlists (reusing Result possibly?).
>> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
>> and get rid of ps_TupFromTlist which is fairly ugly.
>
> Would that not lead to, in effect, duplicating all of execQual.c?  The new
> executor node would still have to be prepared to process all expression
> node types.
>
>> Robert suggested - IIRC mentioning previous on-list discussion - to
>> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
>> that that'd be a larger undertaking, with significant semantics changes.
>
> Yes, this was discussed on-list awhile back (I see David found a reference
> already).  I think it's feasible, although we'd first have to agree
> whether we want to remain bug-compatible with the old
> least-common-multiple-of-the-periods behavior.  I would vote for not,
> but it's certainly a debatable thing.

+1 on removing LCM.

The behavior of multiple targetlist SRF is so bizarre that it's
incredible to believe anyone would reasonably expect it to work that
way. Agree also that casual sane usage of target list SRF is
incredibly common via generate_series() and unnest() etc is
exceptionally common...better not to break those cases without a
better justification than code simplicity.

merlin



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, May 23, 2016 at 1:44 PM, David Fetter <david@fetter.org> wrote:
On Mon, May 23, 2016 at 01:36:57PM -0400, Tom Lane wrote:
> David Fetter <david@fetter.org> writes:
> > On Mon, May 23, 2016 at 01:10:29PM -0400, Tom Lane wrote:
> >> This seems a bridge too far to me.  It's just way too common to do
> >> "select generate_series(1,n)".  We could tell people they have to
> >> rewrite to "select * from generate_series(1,n)", but it would be far
> >> more polite to do that for them.
>
> > How about making "TABLE generate_series(1,n)" work?  It's even
> > shorter in exchange for some cognitive load.
>
> No thanks --- the word after TABLE ought to be a table name, not some
> arbitrary expression.  That's way too much mess to save one keystroke.

It's not just about saving a keystroke.  This change would go with
removing the ability to do SRFs in the target list of a SELECT
query.

​If you want to make an argument for doing this regardless of the target list SRF change by all means - but it does absolutely nothing to mitigate the breakage that would result if we choose this path.

David J.​


Re: Changed SRF in targetlist handling

From
David Fetter
Date:
On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
> On Mon, May 23, 2016 at 12:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Andres Freund <andres@anarazel.de> writes:
> >> discussing executor performance with a number of people at pgcon,
> >> several hackers - me included - complained about the additional
> >> complexity, both code and runtime, required to handle SRFs in the target
> >> list.
> >
> > Yeah, this has been an annoyance for a long time.
> >
> >> One idea I circulated was to fix that by interjecting a special executor
> >> node to process SRF containing targetlists (reusing Result possibly?).
> >> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
> >> and get rid of ps_TupFromTlist which is fairly ugly.
> >
> > Would that not lead to, in effect, duplicating all of execQual.c?  The new
> > executor node would still have to be prepared to process all expression
> > node types.
> >
> >> Robert suggested - IIRC mentioning previous on-list discussion - to
> >> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
> >> that that'd be a larger undertaking, with significant semantics changes.
> >
> > Yes, this was discussed on-list awhile back (I see David found a reference
> > already).  I think it's feasible, although we'd first have to agree
> > whether we want to remain bug-compatible with the old
> > least-common-multiple-of-the-periods behavior.  I would vote for not,
> > but it's certainly a debatable thing.
> 
> +1 on removing LCM.

As a green field project, that would make total sense.  As a thing
decades in, it's not clear to me that that would break less stuff or
break it worse than simply disallowing SRFs in the target list, which
has been rejected on bugward-compatibility grounds.  I suspect it
would be even worse because disallowing SRFs in target lists would at
least be obvious and localized when it broke code.

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate



Re: Changed SRF in targetlist handling

From
Merlin Moncure
Date:
On Mon, May 23, 2016 at 2:13 PM, David Fetter <david@fetter.org> wrote:
> On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
>> On Mon, May 23, 2016 at 12:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> > Andres Freund <andres@anarazel.de> writes:
>> >> discussing executor performance with a number of people at pgcon,
>> >> several hackers - me included - complained about the additional
>> >> complexity, both code and runtime, required to handle SRFs in the target
>> >> list.
>> >
>> > Yeah, this has been an annoyance for a long time.
>> >
>> >> One idea I circulated was to fix that by interjecting a special executor
>> >> node to process SRF containing targetlists (reusing Result possibly?).
>> >> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
>> >> and get rid of ps_TupFromTlist which is fairly ugly.
>> >
>> > Would that not lead to, in effect, duplicating all of execQual.c?  The new
>> > executor node would still have to be prepared to process all expression
>> > node types.
>> >
>> >> Robert suggested - IIRC mentioning previous on-list discussion - to
>> >> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
>> >> that that'd be a larger undertaking, with significant semantics changes.
>> >
>> > Yes, this was discussed on-list awhile back (I see David found a reference
>> > already).  I think it's feasible, although we'd first have to agree
>> > whether we want to remain bug-compatible with the old
>> > least-common-multiple-of-the-periods behavior.  I would vote for not,
>> > but it's certainly a debatable thing.
>>
>> +1 on removing LCM.
>
> As a green field project, that would make total sense.  As a thing
> decades in, it's not clear to me that that would break less stuff or
> break it worse than simply disallowing SRFs in the target list, which
> has been rejected on bugward-compatibility grounds.  I suspect it
> would be even worse because disallowing SRFs in target lists would at
> least be obvious and localized when it broke code.

If I'm reading this correctly, it sounds to me like you are making the
case that removing target list SRF completely would somehow cause less
breakage than say, rewriting it to a LATERAL based implementation for
example.  With more than a little forbearance, let's just say I don't
agree.

merlin



Re: Changed SRF in targetlist handling

From
Joe Conway
Date:
On 05/23/2016 12:39 PM, Merlin Moncure wrote:
> On Mon, May 23, 2016 at 2:13 PM, David Fetter <david@fetter.org> wrote:
>> On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
>>> On Mon, May 23, 2016 at 12:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>>> Andres Freund <andres@anarazel.de> writes:
>>>>> discussing executor performance with a number of people at pgcon,
>>>>> several hackers - me included - complained about the additional
>>>>> complexity, both code and runtime, required to handle SRFs in the target
>>>>> list.
>>>>
>>>> Yeah, this has been an annoyance for a long time.
>>>>
>>>>> One idea I circulated was to fix that by interjecting a special executor
>>>>> node to process SRF containing targetlists (reusing Result possibly?).
>>>>> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
>>>>> and get rid of ps_TupFromTlist which is fairly ugly.
>>>>
>>>> Would that not lead to, in effect, duplicating all of execQual.c?  The new
>>>> executor node would still have to be prepared to process all expression
>>>> node types.
>>>>
>>>>> Robert suggested - IIRC mentioning previous on-list discussion - to
>>>>> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
>>>>> that that'd be a larger undertaking, with significant semantics changes.
>>>>
>>>> Yes, this was discussed on-list awhile back (I see David found a reference
>>>> already).  I think it's feasible, although we'd first have to agree
>>>> whether we want to remain bug-compatible with the old
>>>> least-common-multiple-of-the-periods behavior.  I would vote for not,
>>>> but it's certainly a debatable thing.
>>>
>>> +1 on removing LCM.
>>
>> As a green field project, that would make total sense.  As a thing
>> decades in, it's not clear to me that that would break less stuff or
>> break it worse than simply disallowing SRFs in the target list, which
>> has been rejected on bugward-compatibility grounds.  I suspect it
>> would be even worse because disallowing SRFs in target lists would at
>> least be obvious and localized when it broke code.
>
> If I'm reading this correctly, it sounds to me like you are making the
> case that removing target list SRF completely would somehow cause less
> breakage than say, rewriting it to a LATERAL based implementation for
> example.  With more than a little forbearance, let's just say I don't
> agree.

I'm not necessarily saying that we should totally remove target list
SRFs, but I will point out it has been deprecated ever since SRFs were
first introduced:

http://www.postgresql.org/docs/7.3/static/xfunc-sql.html
 "Currently, functions returning sets may also be called in the target  list of a SELECT query. For each row that the
SELECTgenerates by  itself, the function returning set is invoked, and an output row is  generated for each element of
thefunction's result set. Note,  however, that this capability is deprecated and may be removed in  future releases." 

I would be in favor of rewriting it to a LATERAL, but that would not be
backwards compatible entirely either IIUC.

I'll also note that, unless I missed something, we also have to consider
that the capability to pipeline results is still only available in the
target list.

Joe

--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development


Re: Changed SRF in targetlist handling

From
David Fetter
Date:
On Mon, May 23, 2016 at 02:39:54PM -0500, Merlin Moncure wrote:
> On Mon, May 23, 2016 at 2:13 PM, David Fetter <david@fetter.org> wrote:
> > On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
> >> On Mon, May 23, 2016 at 12:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> > Andres Freund <andres@anarazel.de> writes:
> >> >> discussing executor performance with a number of people at pgcon,
> >> >> several hackers - me included - complained about the additional
> >> >> complexity, both code and runtime, required to handle SRFs in the target
> >> >> list.
> >> >
> >> > Yeah, this has been an annoyance for a long time.
> >> >
> >> >> One idea I circulated was to fix that by interjecting a special executor
> >> >> node to process SRF containing targetlists (reusing Result possibly?).
> >> >> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
> >> >> and get rid of ps_TupFromTlist which is fairly ugly.
> >> >
> >> > Would that not lead to, in effect, duplicating all of execQual.c?  The new
> >> > executor node would still have to be prepared to process all expression
> >> > node types.
> >> >
> >> >> Robert suggested - IIRC mentioning previous on-list discussion - to
> >> >> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
> >> >> that that'd be a larger undertaking, with significant semantics changes.
> >> >
> >> > Yes, this was discussed on-list awhile back (I see David found a reference
> >> > already).  I think it's feasible, although we'd first have to agree
> >> > whether we want to remain bug-compatible with the old
> >> > least-common-multiple-of-the-periods behavior.  I would vote for not,
> >> > but it's certainly a debatable thing.
> >>
> >> +1 on removing LCM.
> >
> > As a green field project, that would make total sense.  As a thing
> > decades in, it's not clear to me that that would break less stuff or
> > break it worse than simply disallowing SRFs in the target list, which
> > has been rejected on bugward-compatibility grounds.  I suspect it
> > would be even worse because disallowing SRFs in target lists would at
> > least be obvious and localized when it broke code.
> 
> If I'm reading this correctly, it sounds to me like you are making the
> case that removing target list SRF completely would somehow cause less
> breakage than say, rewriting it to a LATERAL based implementation for
> example.

Yes.

Making SRFs in target lists throw an error is a thing that will be
pretty straightforward to deal with in extant code bases, whatever
size of pain in the neck it might be.  The line of code that caused
the error would be very clear, and the fix would be very obvious.

Making their behavior different in some way that throws no warnings is
guaranteed to cause subtle and hard to track bugs in extant code
bases.  We lost not a few existing users when we caused similar
knock-ons in 8.3 by removing automated casts to text.

I am no longer advocating for removing the functionality.  I am just
pointing out that the knock-on effects of changing the functionality
may well cause more pain than the ones from removing it entirely.

> With more than a little forbearance, let's just say I don't agree.

If you'd be so kind as to explain your reasons, I think we'd all
benefit.

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Merlin Moncure <mmoncure@gmail.com> writes:
> On Mon, May 23, 2016 at 2:13 PM, David Fetter <david@fetter.org> wrote:
>> On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
>>> +1 on removing LCM.

>> As a green field project, that would make total sense.  As a thing
>> decades in, it's not clear to me that that would break less stuff or
>> break it worse than simply disallowing SRFs in the target list, which
>> has been rejected on bugward-compatibility grounds.  I suspect it
>> would be even worse because disallowing SRFs in target lists would at
>> least be obvious and localized when it broke code.

> If I'm reading this correctly, it sounds to me like you are making the
> case that removing target list SRF completely would somehow cause less
> breakage than say, rewriting it to a LATERAL based implementation for
> example.  With more than a little forbearance, let's just say I don't
> agree.

We should consider single and multiple SRFs in a targetlist as distinct
use-cases; only the latter has got weird properties.

There are several things we could potentially do with multiple SRFs in
the same targetlist.  In increasing order of backwards compatibility and
effort required:

1. Throw error if there's more than one SRF.

2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
have the same behavior as before if the SRFs all return the same number
of rows, and otherwise would behave differently.

3. Rewrite into some other construct that still ends up as a FunctionScan
RTE node, but has the old LCM behavior if the SRFs produce different
numbers of rows.  (Perhaps we would not need to expose this construct
as something directly SQL-visible.)

It's certainly arguable that the common use-cases for SRF-in-tlist
don't have more than one SRF per tlist, and thus that implementing #1
would be an appropriate amount of effort.  It's worth noting also that
the LCM behavior has been repeatedly reported as a bug, and therefore
that if we do #3 we'll be expending very substantial effort to be
literally bug-compatible with ancient behavior that no one in the
current development group thinks is well-designed.  As far as #2 goes,
it would have the advantage that code depending on the same-number-of-
rows case would continue to work as before.  David has a point that it
would silently break application code that's actually depending on the
LCM behavior, but how much of that is there likely to be, really?

[ reflects a bit... ]  I guess there is room for an option 2-and-a-half:

2.5. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...), but decorate
the FunctionScan RTE to tell the executor to throw an error if the SRFs
don't all return the same number of rows, rather than silently
null-padding.  This would have the same behavior as before for the sane
case, and would be very not-silent about cases where apps actually invoked
the LCM behavior.  Again, we wouldn't necessarily have to expose such an
option at the SQL level.  (Though it strikes me that such a restriction
could have value in its own right, analogous to the STRICT options that
we've invented in some other places to allow insisting on the expected
numbers of rows being returned.  ROWS FROM STRICT (srf1(), srf2(), ...),
anybody?)
        regards, tom lane



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, May 23, 2016 at 4:05 PM, David Fetter <david@fetter.org> wrote:
On Mon, May 23, 2016 at 02:39:54PM -0500, Merlin Moncure wrote:
> On Mon, May 23, 2016 at 2:13 PM, David Fetter <david@fetter.org> wrote:
> > On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
> >> On Mon, May 23, 2016 at 12:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> > Andres Freund <andres@anarazel.de> writes:
> >> >> discussing executor performance with a number of people at pgcon,
> >> >> several hackers - me included - complained about the additional
> >> >> complexity, both code and runtime, required to handle SRFs in the target
> >> >> list.
> >> >
> >> > Yeah, this has been an annoyance for a long time.
> >> >
> >> >> One idea I circulated was to fix that by interjecting a special executor
> >> >> node to process SRF containing targetlists (reusing Result possibly?).
> >> >> That'd allow to remove the isDone argument from ExecEval*/ExecProject*
> >> >> and get rid of ps_TupFromTlist which is fairly ugly.
> >> >
> >> > Would that not lead to, in effect, duplicating all of execQual.c?  The new
> >> > executor node would still have to be prepared to process all expression
> >> > node types.
> >> >
> >> >> Robert suggested - IIRC mentioning previous on-list discussion - to
> >> >> instead rewrite targetlist SRFs into lateral joins. My gut feeling is
> >> >> that that'd be a larger undertaking, with significant semantics changes.
> >> >
> >> > Yes, this was discussed on-list awhile back (I see David found a reference
> >> > already).  I think it's feasible, although we'd first have to agree
> >> > whether we want to remain bug-compatible with the old
> >> > least-common-multiple-of-the-periods behavior.  I would vote for not,
> >> > but it's certainly a debatable thing.
> >>
> >> +1 on removing LCM.
> >
> > As a green field project, that would make total sense.  As a thing
> > decades in, it's not clear to me that that would break less stuff or
> > break it worse than simply disallowing SRFs in the target list, which
> > has been rejected on bugward-compatibility grounds.  I suspect it
> > would be even worse because disallowing SRFs in target lists would at
> > least be obvious and localized when it broke code.
>
> If I'm reading this correctly, it sounds to me like you are making the
> case that removing target list SRF completely would somehow cause less
> breakage than say, rewriting it to a LATERAL based implementation for
> example.

Yes.

Making SRFs in target lists throw an error is a thing that will be
pretty straightforward to deal with in extant code bases, whatever
size of pain in the neck it might be.  The line of code that caused
the error would be very clear, and the fix would be very obvious.

Making their behavior different in some way that throws no warnings is
guaranteed to cause subtle and hard to track bugs in extant code
bases. 

​I'm advocating that if a presently allowed SRF-in-target-list is allowed to remain it executes using the same semantics it has today.  In all other cases, including LCM, if the present behavior is undesirable to maintain we throw an error.  I'd hope that such an error can be written in such a way as to name the offending function or functions.

If the user of a complex query doesn't want to expend the effort to locate the specific instance of SRF that is in violation they will still have the option to rewrite all of their uses in that particular query.

David J.

Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Joe Conway <mail@joeconway.com> writes:
> I would be in favor of rewriting it to a LATERAL, but that would not be
> backwards compatible entirely either IIUC.

It could be made so, I think, but it may be more trouble than it's worth;
see my previous message.

> I'll also note that, unless I missed something, we also have to consider
> that the capability to pipeline results is still only available in the
> target list.

Yes, we would definitely want to improve nodeFunctionscan.c to perform
better for ValuePerCall SRFs.  But that has value independently of this.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Alvaro Herrera
Date:
Tom Lane wrote:
> Joe Conway <mail@joeconway.com> writes:

> > I'll also note that, unless I missed something, we also have to consider
> > that the capability to pipeline results is still only available in the
> > target list.
> 
> Yes, we would definitely want to improve nodeFunctionscan.c to perform
> better for ValuePerCall SRFs.  But that has value independently of this.

Ah, so that's what "pipeline results" mean!  I hadn't gotten that.  I
agree; Abhijit had a patch or a plan for this, a long time ago ...

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Merlin Moncure <mmoncure@gmail.com> writes:
> On Mon, May 23, 2016 at 2:13 PM, David Fetter <david@fetter.org> wrote:
>> On Mon, May 23, 2016 at 01:28:11PM -0500, Merlin Moncure wrote:
>>> +1 on removing LCM.

>> As a green field project, that would make total sense.  As a thing
>> decades in, it's not clear to me that that would break less stuff or
>> break it worse than simply disallowing SRFs in the target list, which
>> has been rejected on bugward-compatibility grounds.  I suspect it
>> would be even worse because disallowing SRFs in target lists would at
>> least be obvious and localized when it broke code.

> If I'm reading this correctly, it sounds to me like you are making the
> case that removing target list SRF completely would somehow cause less
> breakage than say, rewriting it to a LATERAL based implementation for
> example.  With more than a little forbearance, let's just say I don't
> agree.

We should consider single and multiple SRFs in a targetlist as distinct
use-cases; only the latter has got weird properties.

There are several things we could potentially do with multiple SRFs in
the same targetlist.  In increasing order of backwards compatibility and
effort required:

1. Throw error if there's more than one SRF.

2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
have the same behavior as before if the SRFs all return the same number
of rows, and otherwise would behave differently.

3. Rewrite into some other construct that still ends up as a FunctionScan
RTE node, but has the old LCM behavior if the SRFs produce different
numbers of rows.  (Perhaps we would not need to expose this construct
as something directly SQL-visible.)

It's certainly arguable that the common use-cases for SRF-in-tlist
don't have more than one SRF per tlist, and thus that implementing #1
would be an appropriate amount of effort.  It's worth noting also that
the LCM behavior has been repeatedly reported as a bug, and therefore
that if we do #3 we'll be expending very substantial effort to be
literally bug-compatible with ancient behavior that no one in the
current development group thinks is well-designed.  As far as #2 goes,
it would have the advantage that code depending on the same-number-of-
rows case would continue to work as before.  David has a point that it
would silently break application code that's actually depending on the
LCM behavior, but how much of that is there likely to be, really?

[ reflects a bit... ]  I guess there is room for an option 2-and-a-half:

2.5. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...), but decorate
the FunctionScan RTE to tell the executor to throw an error if the SRFs
don't all return the same number of rows, rather than silently
null-padding.  This would have the same behavior as before for the sane
case, and would be very not-silent about cases where apps actually invoked
the LCM behavior.  Again, we wouldn't necessarily have to expose such an
option at the SQL level.  (Though it strikes me that such a restriction
could have value in its own right, analogous to the STRICT options that
we've invented in some other places to allow insisting on the expected
numbers of rows being returned.  ROWS FROM STRICT (srf1(), srf2(), ...),
anybody?)

​I'd let the engineers decide between 1, 2.5, and 3 - but if we accomplish our goals while implementing #3 I'd say that would be the best outcome for the community as whole.  

We don't have the luxury of providing a safe-usage mode where people writing new queries get the error but pre-existing queries are considered OK.  We will have to rely upon education and deal with the occasional bug report but our long-time customers, even if only a minority would be affected, will appreciate the effort taken to not break code that has been working for a long time.

The minority is likely small enough to at least make options 1 and 2.5 viable though I'd think we make an effort to avoid #1.

​David J.​

Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, May 23, 2016 at 4:24 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Tom Lane wrote:
> Joe Conway <mail@joeconway.com> writes:

> > I'll also note that, unless I missed something, we also have to consider
> > that the capability to pipeline results is still only available in the
> > target list.
>
> Yes, we would definitely want to improve nodeFunctionscan.c to perform
> better for ValuePerCall SRFs.  But that has value independently of this.

Ah, so that's what "pipeline results" mean!  I hadn't gotten that.  I
agree; Abhijit had a patch or a plan for this, a long time ago ...


​Is this sidebar strictly an implementation detail, not user visible?

David J.

Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
"David G. Johnston" <david.g.johnston@gmail.com> writes:
> On Mon, May 23, 2016 at 4:24 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
> wrote:
>> Ah, so that's what "pipeline results" mean!  I hadn't gotten that.  I
>> agree; Abhijit had a patch or a plan for this, a long time ago ...

> ​Is this sidebar strictly an implementation detail, not user visible?

Hmm.  It could be visible in the sense that the execution of multiple
functions in one ROWS FROM() construct could be interleaved, while
(I think) the current implementation runs each one to completion
serially.  But if you're writing code that assumes that, I think you
should not be very surprised when we break it.  In any case, that
would not affect the proposed translation for SRFs-in-tlist, since
those have that behavior today.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, May 23, 2016 at 4:42 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"David G. Johnston" <david.g.johnston@gmail.com> writes:
> On Mon, May 23, 2016 at 4:24 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
> wrote:
>> Ah, so that's what "pipeline results" mean!  I hadn't gotten that.  I
>> agree; Abhijit had a patch or a plan for this, a long time ago ...

> ​Is this sidebar strictly an implementation detail, not user visible?

Hmm.  It could be visible in the sense that the execution of multiple
functions in one ROWS FROM() construct could be interleaved, while
(I think) the current implementation runs each one to completion
serially.  But if you're writing code that assumes that, I think you
should not be very surprised when we break it.  In any case, that
would not affect the proposed translation for SRFs-in-tlist, since
those have that behavior today.

Thanks

​Sounds like "zipper results" would be a better term for it...but, yes, if that's the general context it falls into implementation from my perspective.​

​But then I don't get Joe's point - if its an implementation detail why should it matter if rewriting the SRF-in-tlist to be laterals changes execution from a serial to an interleaved​ implementation.  Plus, Joe's claim: "the capability to pipeline results is still only available in the target list", and yours above are at odds since you claim the rewritten behavior is the same today.  Is there a disconnect in knowledge or are you talking about different things?

​David J.​

Re: Changed SRF in targetlist handling

From
Joe Conway
Date:
On 05/23/2016 02:37 PM, David G. Johnston wrote:
> ​But then I don't get Joe's point - if its an implementation detail why
> should it matter if rewriting the SRF-in-tlist to be laterals changes
> execution from a serial to an interleaved​ implementation.  Plus, Joe's
> claim: "the capability to pipeline results is still only available in
> the target list", and yours above are at odds since you claim the
> rewritten behavior is the same today.  Is there a disconnect in
> knowledge or are you talking about different things?

Unless there have been recent changes which I missed, ValuePerCall SRFs
are still run to completion in one go, when executed in the FROM clause,
but they project one-row-at-a-time in the target list. If your SRF
returns many-many rows, the problem with the former case is that the
entire thing has to be materialized in memory.

Joe

--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development


Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-05-23 13:10:29 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > One idea I circulated was to fix that by interjecting a special executor
> > node to process SRF containing targetlists (reusing Result possibly?).
> > That'd allow to remove the isDone argument from ExecEval*/ExecProject*
> > and get rid of ps_TupFromTlist which is fairly ugly.
> 
> Would that not lead to, in effect, duplicating all of execQual.c?  The new
> executor node would still have to be prepared to process all expression
> node types.

I don't think it necessarily has to. ISTM that if we add a version of
ExecProject()/ExecTargetList() that continues returning multiple rows,
we can make the knowledge about the one type of expression we allow to
return multiple rows.  That'd require a bit of uglyness to implement
stuff like
SELECT generate_series(1, 2)::text, generate_series(1, 2) * 5;
etc. It seems we'd basically have to do one projection step for the
SRFs, and then another for the rest.  I'm inclined to think that's
acceptable to get rid of a lot of the related uglyness.


> > One issue with removing targetlist SRFs is that they're currently
> > considerably faster than SRFs in FROM:
> 
> I suspect that depends greatly on your test case.  But in any case
> we could put more effort into optimizing nodeFunctionscan.

I doubt you'll find cases where it's significantly the other way round
for percall SRFs. The fundamental issue is that targetlist SRFs don't
have to spill to a tuplestore, whereas nodeFunctionscan ones have to
(even if they're percall).



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On 2016-05-23 13:10:29 -0400, Tom Lane wrote:
>> Would that not lead to, in effect, duplicating all of execQual.c?  The new
>> executor node would still have to be prepared to process all expression
>> node types.

> I don't think it necessarily has to. ISTM that if we add a version of
> ExecProject()/ExecTargetList() that continues returning multiple rows,
> we can make the knowledge about the one type of expression we allow to
> return multiple rows.  That'd require a bit of uglyness to implement
> stuff like
> SELECT generate_series(1, 2)::text, generate_series(1, 2) * 5;
> etc. It seems we'd basically have to do one projection step for the
> SRFs, and then another for the rest.  I'm inclined to think that's
> acceptable to get rid of a lot of the related uglyness.

[ shrug... ]  That seems like it's morally equivalent to (but uglier than)
what I wanted to do, which is to teach the planner to rewrite the query to
put the SRFs into a lateral FROM item.  Splitting the tlist into two
levels will work out to be exactly the same rewriting problem.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-05-25 15:02:23 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-05-23 13:10:29 -0400, Tom Lane wrote:
> >> Would that not lead to, in effect, duplicating all of execQual.c?  The new
> >> executor node would still have to be prepared to process all expression
> >> node types.
> 
> > I don't think it necessarily has to. ISTM that if we add a version of
> > ExecProject()/ExecTargetList() that continues returning multiple rows,
> > we can make the knowledge about the one type of expression we allow to
> > return multiple rows.  That'd require a bit of uglyness to implement
> > stuff like
> > SELECT generate_series(1, 2)::text, generate_series(1, 2) * 5;
> > etc. It seems we'd basically have to do one projection step for the
> > SRFs, and then another for the rest.  I'm inclined to think that's
> > acceptable to get rid of a lot of the related uglyness.
> 
> [ shrug... ]  That seems like it's morally equivalent to (but uglier than)
> what I wanted to do, which is to teach the planner to rewrite the query to
> put the SRFs into a lateral FROM item.  Splitting the tlist into two
> levels will work out to be exactly the same rewriting problem.

I think that depends on how bug compatible we want to be. It seems
harder to get the (rather odd!) lockstep iteration behaviour between two
SRFS with the LATERAL approach?

tpch[6098][1]=# SELECT generate_series(1, 3), generate_series(1,3);
┌─────────────────┬─────────────────┐
│ generate_series │ generate_series │
├─────────────────┼─────────────────┤
│               1 │               1 │
│               2 │               2 │
│               3 │               3 │
└─────────────────┴─────────────────┘
(3 rows)

tpch[6098][1]=# SELECT generate_series(1, 3), generate_series(1,4);
┌─────────────────┬─────────────────┐
│ generate_series │ generate_series │
├─────────────────┼─────────────────┤
│               1 │               1 │
│               2 │               2 │
│               3 │               3 │
│               1 │               4 │
│               2 │               1 │
│               3 │               2 │
│               1 │               3 │
│               2 │               4 │
│               3 │               1 │
│               1 │               2 │
│               2 │               3 │
│               3 │               4 │
└─────────────────┴─────────────────┘
(12 rows)


Regards,

Andres



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On 2016-05-25 15:02:23 -0400, Tom Lane wrote:
>> [ shrug... ]  That seems like it's morally equivalent to (but uglier than)
>> what I wanted to do, which is to teach the planner to rewrite the query to
>> put the SRFs into a lateral FROM item.  Splitting the tlist into two
>> levels will work out to be exactly the same rewriting problem.

> I think that depends on how bug compatible we want to be. It seems
> harder to get the (rather odd!) lockstep iteration behaviour between two
> SRFS with the LATERAL approach?

We could certainly make a variant behavior in nodeFunctionscan.c that
emulates that, if we feel that being exactly bug-compatible on the point
is actually what we want.  I'm dubious about that though, not least
because I don't think *anyone* actually believes that that behavior isn't
broken.  Did you read my upthread message suggesting assorted compromise
choices?
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-05-25 15:20:03 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-05-25 15:02:23 -0400, Tom Lane wrote:
> >> [ shrug... ]  That seems like it's morally equivalent to (but uglier than)
> >> what I wanted to do, which is to teach the planner to rewrite the query to
> >> put the SRFs into a lateral FROM item.  Splitting the tlist into two
> >> levels will work out to be exactly the same rewriting problem.
> 
> > I think that depends on how bug compatible we want to be. It seems
> > harder to get the (rather odd!) lockstep iteration behaviour between two
> > SRFS with the LATERAL approach?
> 
> We could certainly make a variant behavior in nodeFunctionscan.c that
> emulates that, if we feel that being exactly bug-compatible on the point
> is actually what we want.  I'm dubious about that though, not least
> because I don't think *anyone* actually believes that that behavior isn't
> broken.  Did you read my upthread message suggesting assorted compromise
> choices?

You mean https://www.postgresql.org/message-id/21076.1464034513@sss.pgh.pa.us ?
If so, yes.

If we go with rewriting this into LATERAL, I'd vote for 2.5 (trailed by
option 1), that'd keep most of the functionality, and would break
visibly rather than invisibly in the cases where not.

I guess you're not planning to work on this?

Greetings,

Andres Freund



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On 2016-05-25 15:20:03 -0400, Tom Lane wrote:
>> We could certainly make a variant behavior in nodeFunctionscan.c that
>> emulates that, if we feel that being exactly bug-compatible on the point
>> is actually what we want.  I'm dubious about that though, not least
>> because I don't think *anyone* actually believes that that behavior isn't
>> broken.  Did you read my upthread message suggesting assorted compromise
>> choices?

> You mean https://www.postgresql.org/message-id/21076.1464034513@sss.pgh.pa.us ?
> If so, yes.

> If we go with rewriting this into LATERAL, I'd vote for 2.5 (trailed by
> option 1), that'd keep most of the functionality, and would break
> visibly rather than invisibly in the cases where not.

2.5 would be okay with me.

> I guess you're not planning to work on this?

Well, not right now, as it's clearly too late for 9.6.  I might hack on
it later if nobody beats me to it.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Merlin Moncure
Date:
On Wed, May 25, 2016 at 3:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> On 2016-05-25 15:20:03 -0400, Tom Lane wrote:
>>> We could certainly make a variant behavior in nodeFunctionscan.c that
>>> emulates that, if we feel that being exactly bug-compatible on the point
>>> is actually what we want.  I'm dubious about that though, not least
>>> because I don't think *anyone* actually believes that that behavior isn't
>>> broken.  Did you read my upthread message suggesting assorted compromise
>>> choices?
>
>> You mean https://www.postgresql.org/message-id/21076.1464034513@sss.pgh.pa.us ?
>> If so, yes.
>
>> If we go with rewriting this into LATERAL, I'd vote for 2.5 (trailed by
>> option 1), that'd keep most of the functionality, and would break
>> visibly rather than invisibly in the cases where not.
>
> 2.5 would be okay with me.
>
>> I guess you're not planning to work on this?
>
> Well, not right now, as it's clearly too late for 9.6.  I might hack on
> it later if nobody beats me to it.

Curious if this approach will also rewrite:
select generate_series(1,generate_series(1,3)) s;

...into
select s from generate_series(1,3) x cross join lateral generate_series(1,x) s;

another interesting case today is:
create sequence s;
select generate_series(1,nextval('s')), generate_series(1,nextval('s'));

this statement never terminates.  a lateral rewrite of this query
would always terminate with much better defined and well understood
behaviors -- this is good.

merlin



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Merlin Moncure <mmoncure@gmail.com> writes:
> On Wed, May 25, 2016 at 3:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Andres Freund <andres@anarazel.de> writes:
>>> If we go with rewriting this into LATERAL, I'd vote for 2.5 (trailed by
>>> option 1), that'd keep most of the functionality, and would break
>>> visibly rather than invisibly in the cases where not.
>> 2.5 would be okay with me.

> Curious if this approach will also rewrite:
> select generate_series(1,generate_series(1,3)) s;
> ...into
> select s from generate_series(1,3) x cross join lateral generate_series(1,x) s;

Yeah, that would be the idea.

> another interesting case today is:
> create sequence s;
> select generate_series(1,nextval('s')), generate_series(1,nextval('s'));

> this statement never terminates.  a lateral rewrite of this query
> would always terminate with much better defined and well understood
> behaviors -- this is good.

Interesting example demonstrating that 100% bug compatibility is not
possible.  But as you say, most people would probably prefer the other
behavior anyhow.
        regards, tom lane



Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Friday, June 3, 2016, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Merlin Moncure <mmoncure@gmail.com> writes:
> On Wed, May 25, 2016 at 3:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Andres Freund <andres@anarazel.de> writes:
>>> If we go with rewriting this into LATERAL, I'd vote for 2.5 (trailed by
>>> option 1), that'd keep most of the functionality, and would break
>>> visibly rather than invisibly in the cases where not.
>> 2.5 would be okay with me.

> Curious if this approach will also rewrite:
> select generate_series(1,generate_series(1,3)) s;
> ...into
> select s from generate_series(1,3) x cross join lateral generate_series(1,x) s;

Yeah, that would be the idea.


Ok...  It's only a single srf as far as the outer query is concerned so while it is odd the behavior is well defined and can be transformed while giving the same result.
 
> another interesting case today is:
> create sequence s;
> select generate_series(1,nextval('s')), generate_series(1,nextval('s'));

> this statement never terminates.  a lateral rewrite of this query
> would always terminate with much better defined and well understood
> behaviors -- this is good.

Interesting example demonstrating that 100% bug compatibility is not
possible.  But as you say, most people would probably prefer the other
behavior anyhow.


If taking the 2.5 approach this one would fail as opposed to being rewritten.

This could be an exception to the policy in #3 and would be ok in #2.  It would fail in #1.

Given the apparent general consensus for 2.5 and the lack of working field versions of this form the error seems like a no brainer.

David J.

Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
"David G. Johnston" <david.g.johnston@gmail.com> writes:
> On Friday, June 3, 2016, Tom Lane <tgl@sss.pgh.pa.us
> <javascript:_e(%7B%7D,'cvml','tgl@sss.pgh.pa.us');>> wrote:
>> Merlin Moncure <mmoncure@gmail.com> writes:
>>> another interesting case today is:
>>> create sequence s;
>>> select generate_series(1,nextval('s')), generate_series(1,nextval('s'));

> If taking the 2.5 approach this one would fail as opposed to being
> rewritten.

Well, it'd be rewritten and then would fail at runtime because of the SRF
calls not producing the same number of rows.  But even option #3 would not
be strictly bug-compatible because it would (I imagine) evaluate the
arguments of each SRF only once.  The reason this case doesn't terminate
in the current implementation is that it re-evaluates the SRF arguments
each time we start a SRF over.  That's just weird ...
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Robert Haas
Date:
On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> We should consider single and multiple SRFs in a targetlist as distinct
> use-cases; only the latter has got weird properties.
>
> There are several things we could potentially do with multiple SRFs in
> the same targetlist.  In increasing order of backwards compatibility and
> effort required:
>
> 1. Throw error if there's more than one SRF.
>
> 2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
> have the same behavior as before if the SRFs all return the same number
> of rows, and otherwise would behave differently.

I thought the idea was to rewrite it as LATERAL ROWS FROM (srf1()),
LATERAL ROWS FROM (srf2()), ...

The rewrite you propose here seems to NULL-pad rows after the first
SRF is exhausted:

rhaas=# select * from dual, lateral rows from (generate_series(1,3),
generate_series(1,4));  x   | generate_series | generate_series
-------+-----------------+-----------------dummy |               1 |               1dummy |               2 |
   2dummy |               3 |               3dummy |                 |               4
 
(4 rows)

...whereas with a separate LATERAL clause for each row you get this:

rhaas=# select * from dual, lateral rows from (generate_series(1,3))
a, lateral rows from (generate_series(1,4)) b;  x   | a | b
-------+---+---dummy | 1 | 1dummy | 1 | 2dummy | 1 | 3dummy | 1 | 4dummy | 2 | 1dummy | 2 | 2dummy | 2 | 3dummy | 2 |
4dummy| 3 | 1dummy | 3 | 2dummy | 3 | 3dummy | 3 | 4
 
(12 rows)

The latter is how I'd expect SRF-in-targetlist to work.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> 2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
>> have the same behavior as before if the SRFs all return the same number
>> of rows, and otherwise would behave differently.

> I thought the idea was to rewrite it as LATERAL ROWS FROM (srf1()),
> LATERAL ROWS FROM (srf2()), ...

No, because then you get the cross-product of multiple SRFs, not the
run-in-lockstep behavior.

> The rewrite you propose here seems to NULL-pad rows after the first
> SRF is exhausted:

Yes.  That's why I said it's not compatible if the SRFs don't all return
the same number of rows.  It seems like a reasonable definition to me
though, certainly much more reasonable than the current run-until-LCM
behavior.

> The latter is how I'd expect SRF-in-targetlist to work.

That's not even close to how it works now.  It would break *every*
existing application that has multiple SRFs in the tlist, not just
the ones whose SRFs return different numbers of rows.  And I'm not
convinced that it's a more useful behavior.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, Jun 6, 2016 at 11:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> 2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
>> have the same behavior as before if the SRFs all return the same number
>> of rows, and otherwise would behave differently.

> I thought the idea was to rewrite it as LATERAL ROWS FROM (srf1()),
> LATERAL ROWS FROM (srf2()), ...

No, because then you get the cross-product of multiple SRFs, not the
run-in-lockstep behavior.

> The rewrite you propose here seems to NULL-pad rows after the first
> SRF is exhausted:

Yes.  That's why I said it's not compatible if the SRFs don't all return
the same number of rows.  It seems like a reasonable definition to me
though, certainly much more reasonable than the current run-until-LCM
behavior.

​IOW, this is why this mode query has to fail.
 

> The latter is how I'd expect SRF-in-targetlist to work.

That's not even close to how it works now.  It would break *every*
existing application that has multiple SRFs in the tlist, not just
the ones whose SRFs return different numbers of rows.  And I'm not
convinced that it's a more useful behavior.

To clarify, the present behavior is basically a combination of both of Robert's results.

If the SRFs return the same number of rows the first (zippered) result is returned without an NULL padding.

If the SRFs return a different number of rows the LCM behavior kicks in and you get Robert's second result.

SELECT generate_series(1, 4), generate_series(1, 4) ORDER BY 1, 2;
is the same as
SELECT * FROM ROWS FROM ( generate_series(1, 4), generate_series(1, 4) );

BUT

​SELECT generate_series(1, 3), generate_series(1, 4) ORDER BY 1, 2;
is the same as
SELECT * FROM ROWS FROM generate_series(1, 3) a, LATERAL ROWS FROM generate_series(1, 4) b;


Tom's 2.5 proposal basically says we make the former equivalence succeed and have the later one fail.

The rewrite would be unaware of the cardinality of the SRF and so it cannot conditionally rewrite the query.  One of the two must be chosen and the incompatible behavior turned into an error.

David J.

Re: Changed SRF in targetlist handling

From
Vik Fearing
Date:
On 06/06/16 18:30, David G. Johnston wrote:
> To clarify, the present behavior is basically a combination of both of
> Robert's results.
> 
> If the SRFs return the same number of rows the first (zippered) result
> is returned without an NULL padding.
> 
> If the SRFs return a different number of rows the LCM behavior kicks in
> and you get Robert's second result.

No.

> SELECT generate_series(1, 4), generate_series(1, 4) ORDER BY 1, 2;
> is the same as
> SELECT * FROM ROWS FROM ( generate_series(1, 4), generate_series(1, 4) );
> 
> BUT
> 
> ​SELECT generate_series(1, 3), generate_series(1, 4) ORDER BY 1, 2;
> is the same as
> SELECT * FROM ROWS FROM generate_series(1, 3) a, LATERAL ROWS FROM
> generate_series(1, 4) b;

What would you do with:

SELECT generate_series(1, 3), generate_series(1, 6);

?

> Tom's 2.5 proposal basically says we make the former equivalence succeed
> and have the later one fail.
> 
> The rewrite would be unaware of the cardinality of the SRF and so it
> cannot conditionally rewrite the query.  One of the two must be chosen
> and the incompatible behavior turned into an error.


-- 
Vik Fearing                                          +33 6 46 75 15 36
http://2ndQuadrant.fr     PostgreSQL : Expertise, Formation et Support



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
"David G. Johnston" <david.g.johnston@gmail.com> writes:
> If the SRFs return a different number of rows the LCM behavior kicks in and
> you get Robert's second result.

Only if the periods of the SRFs are relatively prime.  That is, neither of
his examples demonstrate the full weirdness of the current behavior; for
that, you need periods that are multiples of each other.  For instance:

SELECT generate_series(1, 2), generate_series(1, 4); generate_series | generate_series 
-----------------+-----------------              1 |               1              2 |               2              1 |
            3              2 |               4
 
(4 rows)

That doesn't comport with any behavior available from LATERAL.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Robert Haas
Date:
On Mon, Jun 6, 2016 at 11:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> 2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
>>> have the same behavior as before if the SRFs all return the same number
>>> of rows, and otherwise would behave differently.
>
>> I thought the idea was to rewrite it as LATERAL ROWS FROM (srf1()),
>> LATERAL ROWS FROM (srf2()), ...
>
> No, because then you get the cross-product of multiple SRFs, not the
> run-in-lockstep behavior.

Oh.  I assumed that was the expected behavior.  But, ah, what do I know?

>> The rewrite you propose here seems to NULL-pad rows after the first
>> SRF is exhausted:
>
> Yes.  That's why I said it's not compatible if the SRFs don't all return
> the same number of rows.  It seems like a reasonable definition to me
> though, certainly much more reasonable than the current run-until-LCM
> behavior.

I can't argue with that.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Changed SRF in targetlist handling

From
Alvaro Herrera
Date:
Robert Haas wrote:
> On Mon, Jun 6, 2016 at 11:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Robert Haas <robertmhaas@gmail.com> writes:
> >> On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >>> 2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
> >>> have the same behavior as before if the SRFs all return the same number
> >>> of rows, and otherwise would behave differently.
> >
> >> I thought the idea was to rewrite it as LATERAL ROWS FROM (srf1()),
> >> LATERAL ROWS FROM (srf2()), ...
> >
> > No, because then you get the cross-product of multiple SRFs, not the
> > run-in-lockstep behavior.
> 
> Oh.  I assumed that was the expected behavior.  But, ah, what do I know?

Lots, I assume -- but in this case, probably next to nothing, just like
most of us, because what sane person or application would be really
relying on the wacko historical behavior, in order to generate some
collective knowledge?  However, I think that it is possible that
someone, somewhere has two SRFs-in-targetlist that return the same
number of rows and that the current implementation works fine for them;
if we redefine it to work differently, we would break their application
silently, which seems a worse problem than breaking it noisily while
providing an easy way forward (which is to move SRFs to the FROM list)

My vote is to raise an error in the case of more than one SRF in targetlist.

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, Jun 6, 2016 at 2:31 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Robert Haas wrote:
> On Mon, Jun 6, 2016 at 11:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Robert Haas <robertmhaas@gmail.com> writes:
> >> On Mon, May 23, 2016 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >>> 2. Rewrite into LATERAL ROWS FROM (srf1(), srf2(), ...).  This would
> >>> have the same behavior as before if the SRFs all return the same number
> >>> of rows, and otherwise would behave differently.
> >
> >> I thought the idea was to rewrite it as LATERAL ROWS FROM (srf1()),
> >> LATERAL ROWS FROM (srf2()), ...
> >
> > No, because then you get the cross-product of multiple SRFs, not the
> > run-in-lockstep behavior.
>
> Oh.  I assumed that was the expected behavior.  But, ah, what do I know?

Lots, I assume -- but in this case, probably next to nothing, just like
most of us, because what sane person or application would be really
relying on the wacko historical behavior, in order to generate some
collective knowledge?  However, I think that it is possible that
someone, somewhere has two SRFs-in-targetlist that return the same
number of rows and that the current implementation works fine for them;
if we redefine it to work differently, we would break their application
silently, which seems a worse problem than breaking it noisily while
providing an easy way forward (which is to move SRFs to the FROM list)

My vote is to raise an error in the case of more than one SRF in targetlist.

​As long as someone is willing to put in the effort we can make a subset of these multiple-SRFs-in-targetlist queries work without any change in the tabular output, though the processing mechanism might change.​  Your vote is essentially #1 up-thread which seems the most draconian.  Assuming a viable option 2.5 or 3 solution is presented would you vote against it being committed?  If so I'd like to understand why.  I see #1 as basically OK only if their are technical barriers we cannot overcome - including performance.

Link to the definition of the various options Tom proposed:


David J.
 

Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
> Robert Haas wrote:
>> On Mon, Jun 6, 2016 at 11:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> No, because then you get the cross-product of multiple SRFs, not the
>>> run-in-lockstep behavior.

>> Oh.  I assumed that was the expected behavior.  But, ah, what do I know?

> Lots, I assume -- but in this case, probably next to nothing, just like
> most of us, because what sane person or application would be really
> relying on the wacko historical behavior, in order to generate some
> collective knowledge?  However, I think that it is possible that
> someone, somewhere has two SRFs-in-targetlist that return the same
> number of rows and that the current implementation works fine for them;

Yes.  Run-in-lockstep is an extremely useful behavior, so much so that
we made a LATERAL variant for it.  I do not see a reason to break such
cases in the targetlist.

> My vote is to raise an error in the case of more than one SRF in targetlist.

Note that that risks breaking cases that the user does not think are "more
than one SRF".  Consider this example using a regression-test table:

regression=# create function foo() returns setof int8_tbl as
regression-# 'select * from int8_tbl' language sql;
CREATE FUNCTION
regression=# select foo();                foo                  

--------------------------------------(123,456)(123,4567890123456789)(4567890123456789,123)(4567890123456789,4567890123456789)(4567890123456789,-4567890123456789)
(5 rows)

regression=# explain verbose select foo();                 QUERY PLAN                  
----------------------------------------------Result  (cost=0.00..5.25 rows=1000 width=32)  Output: foo()
(2 rows)

regression=# select (foo()).*;       q1        |        q2         
------------------+-------------------             123 |               456             123 |
45678901234567894567890123456789|               1234567890123456789 |  45678901234567894567890123456789 |
-4567890123456789
(5 rows)

regression=# explain verbose select (foo()).*;                 QUERY PLAN                  
----------------------------------------------Result  (cost=0.00..5.50 rows=1000 width=16)  Output: (foo()).q1,
(foo()).q2
(2 rows)

The reason we can get away with this simplistic treatment of
composite-returning SRFs is precisely the run-in-lockstep behavior.
Otherwise the second query would have returned 25 rows.

Now, if we decide to try to rewrite tlist SRFs as LATERAL, it would likely
behoove us to do that rewrite before expanding * not after, so that we can
eliminate the multiple evaluation of foo() that happens currently.  (That
makes it a parser problem not a planner problem.)  And maybe we should
rewrite non-SRF composite-returning functions this way too, because people
have definitely complained about the extra evaluations in that context.
But my point here is that lockstep evaluation does have practical use
when the SRFs are iterating over matching collections of generated rows.
And that seems like a pretty common use-case.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Robert Haas
Date:
On Mon, Jun 6, 2016 at 2:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Now, if we decide to try to rewrite tlist SRFs as LATERAL, it would likely
> behoove us to do that rewrite before expanding * not after, so that we can
> eliminate the multiple evaluation of foo() that happens currently.  (That
> makes it a parser problem not a planner problem.)  And maybe we should
> rewrite non-SRF composite-returning functions this way too, because people
> have definitely complained about the extra evaluations in that context.
> But my point here is that lockstep evaluation does have practical use
> when the SRFs are iterating over matching collections of generated rows.
> And that seems like a pretty common use-case.

Yeah, OK.  I'm not terribly opposed to going that way.  I think the
current behavior sucks badly enough - both because the semantics are
bizarre and because it complicates the whole executor for a niche
feature - that it's worth taking a backward compatibility hit to
change it.  I guess I'd prefer #2 to #2.5, #2.5 to #3, and #3 to #1.
I really don't like #1 much - I think I'd almost rather do nothing.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> ... I guess I'd prefer #2 to #2.5, #2.5 to #3, and #3 to #1.
> I really don't like #1 much - I think I'd almost rather do nothing.

FWIW, that's about my evaluation of the alternatives as well.  I fear
that #1 would get a lot of pushback.  If we think that something like
"LATERAL ROWS FROM STRICT" is worth having on its own merits, then
doing #2.5 seems worthwhile to me, but otherwise I'm just as happy
with #2.  David J. seems to feel that throwing an error (as in #2.5)
rather than silently behaving incompatibly (as in #2) is important,
but I'm not convinced.  In a green field I think we'd prefer #2 over
#2.5, so I'd rather go that direction.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
"David G. Johnston"
Date:
On Mon, Jun 6, 2016 at 3:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
> ... I guess I'd prefer #2 to #2.5, #2.5 to #3, and #3 to #1.
> I really don't like #1 much - I think I'd almost rather do nothing.

FWIW, that's about my evaluation of the alternatives as well.  I fear
that #1 would get a lot of pushback.  If we think that something like
"LATERAL ROWS FROM STRICT" is worth having on its own merits, then
doing #2.5 seems worthwhile to me, but otherwise I'm just as happy
with #2.  David J. seems to feel that throwing an error (as in #2.5)
rather than silently behaving incompatibly (as in #2) is important,
but I'm not convinced.  In a green field I think we'd prefer #2 over
#2.5, so I'd rather go that direction.

​I suspect the decision to error or not is a one or two line change in whatever form the final patch takes.  It seems like approach #2 is acceptable on a theoretical level which implies there is no desire to make the existing LCM behavior available post-patch.

Assuming it is simple then everyone will have a chance to make their opinion known on whether the 2.0 or 2.5 variation is preferable for the final commit.  If a decision needs to be made sooner due to a design decision I'd hope the author of the patch would make that known so we can bring this to resolution at that point instead.

David J.

Re: Changed SRF in targetlist handling

From
Robert Haas
Date:
On Mon, Jun 6, 2016 at 3:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> ... I guess I'd prefer #2 to #2.5, #2.5 to #3, and #3 to #1.
>> I really don't like #1 much - I think I'd almost rather do nothing.
>
> FWIW, that's about my evaluation of the alternatives as well.  I fear
> that #1 would get a lot of pushback.  If we think that something like
> "LATERAL ROWS FROM STRICT" is worth having on its own merits, then
> doing #2.5 seems worthwhile to me, but otherwise I'm just as happy
> with #2.  David J. seems to feel that throwing an error (as in #2.5)
> rather than silently behaving incompatibly (as in #2) is important,
> but I'm not convinced.  In a green field I think we'd prefer #2 over
> #2.5, so I'd rather go that direction.

Same here.  That behavior is actually potentially quite useful, right?Like, you might want to rely on the
NULL-extensionthing, if it were
 
documented as behavior you can count on?

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-05-25 16:55:23 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-05-25 15:20:03 -0400, Tom Lane wrote:
> >> We could certainly make a variant behavior in nodeFunctionscan.c that
> >> emulates that, if we feel that being exactly bug-compatible on the point
> >> is actually what we want.  I'm dubious about that though, not least
> >> because I don't think *anyone* actually believes that that behavior isn't
> >> broken.  Did you read my upthread message suggesting assorted compromise
> >> choices?
>
> > You mean https://www.postgresql.org/message-id/21076.1464034513@sss.pgh.pa.us ?
> > If so, yes.
>
> > If we go with rewriting this into LATERAL, I'd vote for 2.5 (trailed by
> > option 1), that'd keep most of the functionality, and would break
> > visibly rather than invisibly in the cases where not.
>
> 2.5 would be okay with me.
>
> > I guess you're not planning to work on this?
>
> Well, not right now, as it's clearly too late for 9.6.  I might hack on
> it later if nobody beats me to it.

FWIW, as it's blocking my plans for executor related rework (expression
evaluation, batch processing) I started to hack on this.

I've an implementation that

1) turns all targetlist SRF (tSRF from now on) into ROWS FROM  expressions. If there's tSRFs in the argument of a tSRF
thosebecomes  a separate, lateral, ROWS FROM expression.
 

2) If grouping/window functions are present, the entire query is wrapped  in a subquery RTE, except for the
set-returningfunction. All  referenced Var|Aggref|GroupingFunc|WindowFunc|Param nodes in the  original targetlist are
madeto reference that subquery, which gets a  TargetEntry for them.
 

3) If sortClause does *not* reference any tSRFs the sorting is evaluated  in a subquery, to preserve the output
orderingof SRFs in queries  like  SELECT id, generate_series(1,3) FROM (VALUES(1),(2)) d(id) ORDER BY id DESC;  if in
contrastsortClause does reference the tSRF output, it's  evaluated in the outer SRF.
 

this seems to generally work, and allows to remove considerable amounts
of code.

So far I have one problem without an easy solution: Historically queries
like
=# SELECT id, generate_series(1,2) FROM (VALUES(1),(2)) few(id);
┌────┬─────────────────┐
│ id │ generate_series │
├────┼─────────────────┤
│  1 │               1 │
│  1 │               2 │
│  2 │               1 │
│  2 │               2 │
└────┴─────────────────┘
have preserved the SRF output ordering. But by turning the SRF into a
ROWS FROM, there's no guarantee that the cross join between "few" and
generate_series(1,3) above is implemented in that order. I.e. we can get
something like
┌────┬─────────────────┐
│ id │ generate_series │
├────┼─────────────────┤
│  1 │               1 │
│  2 │               1 │
│  1 │               2 │
│  2 │               2 │
└────┴─────────────────┘
because it's implemented as
┌──────────────────────────────────────────────────────────────────────────────┐
│                                  QUERY PLAN                                  │
├──────────────────────────────────────────────────────────────────────────────┤
│ Nested Loop  (cost=0.00..35.03 rows=2000 width=8)                            │
│   ->  Function Scan on generate_series  (cost=0.00..10.00 rows=1000 width=4) │
│   ->  Materialize  (cost=0.00..0.04 rows=2 width=4)                          │
│         ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=4)      │
└──────────────────────────────────────────────────────────────────────────────┘

I right now see no easy and nice-ish way to constrain that.


Besides that I'm structurally wondering whether turning the original
query into a subquery is the right thing to do. It requires some kind of
ugly munching of Query->*, and has the above problem. One alternative
would be to instead perform the necessary magic in grouping_planner(),
by "manually" adding nestloop joins before/after create_ordered_paths()
(depending on SRFs being referenced in the sort clause).  That'd create
plans we'd not have created so far, by layering NestLoop and
FunctionScan nodes above the normal query - that'd allow us to to easily
force the ordering of SRF evaluation.


If we go the subquery route, I'm wondering about where to tackle the
restructuring. So far I'm doing it very early in subquery_planner() -
otherwise the aggregation/sorting/... behaviour is easier to handle.
Perhaps doing it in standard_planner() itself would be better though.
An alternative approach would be to do this during parse-analysis, but I
think that might end up being confusing, because stored rules would
suddenly have a noticeably different structure, and it'd tie us a lot
more to the details of that transformation than I'd like.


Besides the removal of the least-common-multiple behaviour of tSRF queries,
there's some other consequences that using function scans have:
Previously if a tSRF was never evaluated, it didn't cause the number of
rows from being increased. E.g.
SELECT id, COALESCE(1, generate_series(1,2)) FROM (VALUES(1),(2)) few(id);
only produced two rows.  But using joins means that a simple
implementation of using ROWS FROM returns four rows.   We could try to
inject sufficient join conditions in that type of query, to prune down
the number of rows again, but I really don't want to go there - it's
kinda hard in the general case...


Comments?


Regards,


Andres



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> I've an implementation that

> 1) turns all targetlist SRF (tSRF from now on) into ROWS FROM
>    expressions. If there's tSRFs in the argument of a tSRF those becomes
>    a separate, lateral, ROWS FROM expression.

> 2) If grouping/window functions are present, the entire query is wrapped
>    in a subquery RTE, except for the set-returning function. All
>    referenced Var|Aggref|GroupingFunc|WindowFunc|Param nodes in the
>    original targetlist are made to reference that subquery, which gets a
>    TargetEntry for them.

FWIW, I'd be inclined to do the subquery RTE all the time, adding some
optimization fence to ensure it doesn't get folded back.  That fixes
your problem here:

> So far I have one problem without an easy solution: Historically queries
> like
> =# SELECT id, generate_series(1,2) FROM (VALUES(1),(2)) few(id);
> ┌────┬─────────────────┐
> │ id │ generate_series │
> ├────┼─────────────────┤
> │  1 │               1 │
> │  1 │               2 │
> │  2 │               1 │
> │  2 │               2 │
> └────┴─────────────────┘
> have preserved the SRF output ordering. But by turning the SRF into a
> ROWS FROM, there's no guarantee that the cross join between "few" and
> generate_series(1,3) above is implemented in that order.


> Besides that I'm structurally wondering whether turning the original
> query into a subquery is the right thing to do. It requires some kind of
> ugly munching of Query->*, and has the above problem.

It does not seem like it should be that hard, certainly no worse than
subquery pullup.  Want to show code?

> An alternative approach would be to do this during parse-analysis, but I
> think that might end up being confusing, because stored rules would
> suddenly have a noticeably different structure, and it'd tie us a lot
> more to the details of that transformation than I'd like.

-1 on that; we do not want this transformation visible in stored rules.

> Besides the removal of the least-common-multiple behaviour of tSRF queries,
> there's some other consequences that using function scans have:
> Previously if a tSRF was never evaluated, it didn't cause the number of
> rows from being increased. E.g.
> SELECT id, COALESCE(1, generate_series(1,2)) FROM (VALUES(1),(2)) few(id);
> only produced two rows.  But using joins means that a simple
> implementation of using ROWS FROM returns four rows.

Hmm.  I don't mind changing behavior in that sort of corner case.
If we're prepared to discard the LCM behavior, this seems at least
an order of magnitude less likely to be worth worrying about.

Having said that, I do seem to recall a bug report about misbehavior when
a SRF was present in just one arm of a CASE statement.  That would have
the same type of behavior as you describe here, and evidently there's at
least one person out there depending on it.

Would it be worth detecting SRFs below CASE/COALESCE/etc and throwing
an error?  It would be easier to sell throwing an error than silently
changing behavior, I think.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-08-02 19:02:38 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > I've an implementation that
>
> > 1) turns all targetlist SRF (tSRF from now on) into ROWS FROM
> >    expressions. If there's tSRFs in the argument of a tSRF those becomes
> >    a separate, lateral, ROWS FROM expression.
>
> > 2) If grouping/window functions are present, the entire query is wrapped
> >    in a subquery RTE, except for the set-returning function. All
> >    referenced Var|Aggref|GroupingFunc|WindowFunc|Param nodes in the
> >    original targetlist are made to reference that subquery, which gets a
> >    TargetEntry for them.
>
> FWIW, I'd be inclined to do the subquery RTE all the time,

Yea, that's what I ended up doing.


> adding some
> optimization fence to ensure it doesn't get folded back.  That fixes
> your problem here:

> > So far I have one problem without an easy solution: Historically queries
> > like
> > =# SELECT id, generate_series(1,2) FROM (VALUES(1),(2)) few(id);
> > ┌────┬─────────────────┐
> > │ id │ generate_series │
> > ├────┼─────────────────┤
> > │  1 │               1 │
> > │  1 │               2 │
> > │  2 │               1 │
> > │  2 │               2 │
> > └────┴─────────────────┘
> > have preserved the SRF output ordering. But by turning the SRF into a
> > ROWS FROM, there's no guarantee that the cross join between "few" and
> > generate_series(1,3) above is implemented in that order.

But I don't see how that fixes the above problem?  The join, on the
top-level because of aggregates, still can be implemented as
subquery join srf or as srf join subquery, with the different output order
that implies.  I've duct-taped together a solution for that, by forcing
the lateral machinery to always see a dependency from the SRF to the
subquery; but that probably needs a nicer fix than a RangeTblEntry->deps
field which is processed in extract_lateral_references() ;)


> > Besides that I'm structurally wondering whether turning the original
> > query into a subquery is the right thing to do. It requires some kind of
> > ugly munching of Query->*, and has the above problem.
>
> It does not seem like it should be that hard, certainly no worse than
> subquery pullup.  Want to show code?

It's not super hard, there's some stuff like pushing/not-pushing
various sortgrouprefs to the subquery. But I think we can live with it.

Let me clean up the code some, hope to have something today or tomorrow.


> > An alternative approach would be to do this during parse-analysis, but I
> > think that might end up being confusing, because stored rules would
> > suddenly have a noticeably different structure, and it'd tie us a lot
> > more to the details of that transformation than I'd like.
>
> -1 on that; we do not want this transformation visible in stored rules.

Agreed.


> > Besides the removal of the least-common-multiple behaviour of tSRF queries,
> > there's some other consequences that using function scans have:
> > Previously if a tSRF was never evaluated, it didn't cause the number of
> > rows from being increased. E.g.
> > SELECT id, COALESCE(1, generate_series(1,2)) FROM (VALUES(1),(2)) few(id);
> > only produced two rows.  But using joins means that a simple
> > implementation of using ROWS FROM returns four rows.
>
> Hmm.  I don't mind changing behavior in that sort of corner case.
> If we're prepared to discard the LCM behavior, this seems at least
> an order of magnitude less likely to be worth worrying about.

I think it's fine, and potentially less confusing.


> Would it be worth detecting SRFs below CASE/COALESCE/etc and throwing
> an error?  It would be easier to sell throwing an error than silently
> changing behavior, I think.

Hm. We could, but I think the new behaviour would actually make sense in
the long run. Interpreting the coalesce to run on the output of the SRF
doesn't seem bad to me.


I found another edgecase, which we need to make a decision about.
'record' returning SRFs can't be transformed easily into a ROWS
FROM. Consider e.g. the following from the regression tests:

create function array_to_set(anyarray) returns setof record as $$ select i AS "index", $1[i] AS "value" from
generate_subscripts($1,1)  i
 
$$ language sql strict immutable;

select array_to_set(array['one', 'two']);
┌──────────────┐
│ array_to_set │
├──────────────┤
│ (1,one)      │
│ (2,two)      │
└──────────────┘
(2 rows)

which currently works. That currently can't be modeled as ROWS FROM()
directly, because that desperately wants to return the columns as
columns, which we can't do for 'record' returning things, because they
don't have defined columns.  For composite returning SRFs I've currently
implemented that by generating a ROWS() expression, but that doesn't
work for record.

So it seems like we need some, not necessarily user exposed, way of
making nodeFunctionscan.c return the return value as one datum.  One
way, as suggested by Andrew G. on IRC, was to interpret empty column
definition in ROWS FROM interpreted that way.

Greetings,

Andres Freund



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-08-02 16:30:55 -0700, Andres Freund wrote:
> > > Besides that I'm structurally wondering whether turning the original
> > > query into a subquery is the right thing to do. It requires some kind of
> > > ugly munching of Query->*, and has the above problem.
> >
> > It does not seem like it should be that hard, certainly no worse than
> > subquery pullup.  Want to show code?
>
> It's not super hard, there's some stuff like pushing/not-pushing
> various sortgrouprefs to the subquery. But I think we can live with it.
>
> Let me clean up the code some, hope to have something today or
> tomorrow.

Here we go.  This *clearly* is a POC, not more.  But it mostly works.


0001 - adds some test, some of those change after the later patches
0002 - main SRF via ROWS FROM () implementation
0003 - Large patch removing now unused code. Most satisfying.


The interesting bit is obviously 0002. What it basically does is, at the beginning
of subquery_planner():
1) unsrfify:
   move the jointree into a subquery
2) unsrfify_reference_subquery_mutator:
   process the old targetlist to reference the new subquery. If a
   TargetEntry doesn't contain a set, it's entirely moved into the
   subquery. Otherwise all Vars/Aggrefs/... it references are moved to
   the subquery, and referenced in the outer query's target list.
3) unsrfify_implement_srfs_mutator:
   Replace set returning functions in the targetlist with references to
   a new FUNCTION RTE. All non-nested tSRFs are part of the same RTE
   (i.e. the least common multiple behaviour is gone). all tSRFs in
   arguments are implemented as another FUNCTION RTE.

I discovered that we allow SRFs in UPDATE target lists. It's not clear
to me what that's supposed to mean. Nor how exactly to implement that,
given expand_targetlist(). Right now that fails with the patch, because
it re-inserts Var's for the relation replaced by the subquery.

Note that I've not bothered to fix up the regression test output - I'm
certain that explain output and such will still change.

Biggest questions / tasks:
* General approach
* DML handling
* Operator implementation
* SETOF record handling
* correct handling of lateral dependency from RTE to subquery to force
  evaluation order, instead of my RangeTblEntry->deps hack.
* lot of cleanup

Comments?

Greetings,

Andres Freund

Attachment

Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-08-03 20:22:03 -0700, Andres Freund wrote:
> On 2016-08-02 16:30:55 -0700, Andres Freund wrote:
> > > > Besides that I'm structurally wondering whether turning the original
> > > > query into a subquery is the right thing to do. It requires some kind of
> > > > ugly munching of Query->*, and has the above problem.
> > >
> > > It does not seem like it should be that hard, certainly no worse than
> > > subquery pullup.  Want to show code?
> >
> > It's not super hard, there's some stuff like pushing/not-pushing
> > various sortgrouprefs to the subquery. But I think we can live with it.
> >
> > Let me clean up the code some, hope to have something today or
> > tomorrow.
> 
> Here we go.  This *clearly* is a POC, not more.  But it mostly works.
> 
> 
> 0001 - adds some test, some of those change after the later patches
> 0002 - main SRF via ROWS FROM () implementation
> 0003 - Large patch removing now unused code. Most satisfying.
> 
> 
> The interesting bit is obviously 0002. What it basically does is, at the beginning
> of subquery_planner():
> 1) unsrfify:
>    move the jointree into a subquery
> 2) unsrfify_reference_subquery_mutator:
>    process the old targetlist to reference the new subquery. If a
>    TargetEntry doesn't contain a set, it's entirely moved into the
>    subquery. Otherwise all Vars/Aggrefs/... it references are moved to
>    the subquery, and referenced in the outer query's target list.
> 3) unsrfify_implement_srfs_mutator:
>    Replace set returning functions in the targetlist with references to
>    a new FUNCTION RTE. All non-nested tSRFs are part of the same RTE
>    (i.e. the least common multiple behaviour is gone). all tSRFs in
>    arguments are implemented as another FUNCTION RTE.
> 
> I discovered that we allow SRFs in UPDATE target lists. It's not clear
> to me what that's supposed to mean. Nor how exactly to implement that,
> given expand_targetlist(). Right now that fails with the patch, because
> it re-inserts Var's for the relation replaced by the subquery.
> 
> Note that I've not bothered to fix up the regression test output - I'm
> certain that explain output and such will still change.
> 
> Biggest questions / tasks:
> * General approach
> * DML handling
> * Operator implementation
> * SETOF record handling
> * correct handling of lateral dependency from RTE to subquery to force
>   evaluation order, instead of my RangeTblEntry->deps hack.
> * lot of cleanup
> 
> Comments?

Tom, do you think this is roughly going in the right direction? My plan
here is to develop two patches, to come before this:

a) Allow to avoid using a tuplestore for SRF_PERCALL SRFs in ROWS FROM -  otherwise our performance would regress
noticeablyin some cases.
 
b) Allow ROWS FROM() to return SETOF RECORD type SRFs as one column,  instead of expanded. That's important to be able
moveSETOF RECORD  returning functions in the targetlist into ROWS FROM, which otherwise  requires an explicit column
list.

Greetings,

Andres Freund



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-08-17 17:41:28 -0700, Andres Freund wrote:
> Tom, do you think this is roughly going in the right direction? My plan
> here is to develop two patches, to come before this:
> 
> a) Allow to avoid using a tuplestore for SRF_PERCALL SRFs in ROWS FROM -
>    otherwise our performance would regress noticeably in some cases.
> b) Allow ROWS FROM() to return SETOF RECORD type SRFs as one column,
>    instead of expanded. That's important to be able move SETOF RECORD
>    returning functions in the targetlist into ROWS FROM, which otherwise
>    requires an explicit column list.

I'm working on these. Atm ExecMakeTableFunctionResult() resides in
execQual.c - I'm inlining it into nodeFunctionscan.c now, because
there's no other callers, and having it separate seems to bring no
benefit.

Please speak soon up if you disagree.

Andres



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
Hi,

On 2016-05-23 09:26:03 +0800, Craig Ringer wrote:
> SRFs-in-tlist are a lot faster for lockstep iteration etc. They're also
> much simpler to write, though if the result result rowcount differs
> unexpectedly between the functions you get exciting and unexpected
> behaviour.
> 
> WITH ORDINALITY provides what I think is the last of the functionality
> needed to replace SRFs-in-from, but at a syntatactic complexity and
> performance cost. The following example demonstrates that, though it
> doesn't do anything that needs LATERAL etc. I'm aware the following aren't
> semantically identical if the rowcounts differ.

I think here you're just missing ROWS FROM (generate_series(..), generate_series(...))

Andres



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On 2016-08-17 17:41:28 -0700, Andres Freund wrote:
>> Tom, do you think this is roughly going in the right direction?

I've not had time to look at this patch, I'm afraid.  If you still
want me to, I can make time in a day or so.

> I'm working on these. Atm ExecMakeTableFunctionResult() resides in
> execQual.c - I'm inlining it into nodeFunctionscan.c now, because
> there's no other callers, and having it separate seems to bring no
> benefit.

> Please speak soon up if you disagree.

I think ExecMakeTableFunctionResult was placed in execQual.c because
it seemed to belong there alongside the support for SRFs in tlists.
If that's going away then there's no good reason not to move the logic
to where it's used.
        regards, tom lane



Hi,

as noted in [1] I started hacking on removing the current implementation
of SRFs in the targetlist (tSRFs henceforth). IM discussion brought the
need for a description of the problem, need and approach to light.

There are several reasons for wanting to get rid of tSRFs. The primary
ones in my opinion are that the current behaviour of several SRFs in one
targetlist is confusing, and that the implementation burden currently is
all over the executor.  Especially the latter is what is motivating me
working on this, because it blocks my work on making the executor faster
for queries involving significant amounts of tuples.  Batching is hard
if random places in the querytree can icnrease the number of tuples.

The basic idea, hinted at in several threads, is, at plan time, to convert a query like
SELECT generate_series(1, 10);
into
SELECT generate_series FROM ROWS FROM(generate_series(1, 10));

thereby avoiding the complications in the executor (c.f. execQual.c
handling of isDone/ExprMultipleResult and supporting code in many
executor nodes / node->*.ps.ps_TupFromTlist).

There are several design questions along the way:

1) How to deal with the least-common-multiple behaviour of tSRFs. E.g.
=# SELECT generate_series(1, 3), generate_series(1,2);
returning
┌─────────────────┬─────────────────┐
│ generate_series │ generate_series │
├─────────────────┼─────────────────┤
│               1 │               1 │
│               2 │               2 │
│               3 │               1 │
│               1 │               2 │
│               2 │               1 │
│               3 │               2 │
└─────────────────┴─────────────────┘
(6 rows)
but
=# SELECT generate_series(1, 3), generate_series(5,7);
returning
┌─────────────────┬─────────────────┐
│ generate_series │ generate_series │
├─────────────────┼─────────────────┤
│               1 │               5 │
│               2 │               6 │
│               3 │               7 │
└─────────────────┴─────────────────┘

discussion in this thread came, according to my reading, to the
conclusion that that behaviour is just confusing and that the ROWS FROM
behaviour of
=# SELECT * FROM ROWS FROM(generate_series(1, 3), generate_series(1,2));
┌─────────────────┬─────────────────┐
│ generate_series │ generate_series │
├─────────────────┼─────────────────┤
│               1 │               1 │
│               2 │               2 │
│               3 │          (null) │
└─────────────────┴─────────────────┘
(3 rows)

makes more sense.  We also discussed erroring out if two SRFs  return
differing amount of rows, but that seems not to be preferred so far. And
we can easily add it if we want.


2) A naive conversion to ROWS FROM, like in the example in the  introductory paragraph, can change the output, when
implementedas a  join from ROWS FROM to the rest of the query, rather than the other  way round. E.g.
 
=# EXPLAIN SELECT * FROM few, ROWS FROM(generate_series(1,10));
┌──────────────────────────────────────────────────────────────────────────────┐
│                                  QUERY PLAN                                  │
├──────────────────────────────────────────────────────────────────────────────┤
│ Nested Loop  (cost=0.00..36.03 rows=2000 width=8)                            │
│   ->  Function Scan on generate_series  (cost=0.00..10.00 rows=1000 width=4) │
│   ->  Materialize  (cost=0.00..1.03 rows=2 width=4)                          │
│         ->  Seq Scan on few  (cost=0.00..1.02 rows=2 width=4)                │
└──────────────────────────────────────────────────────────────────────────────┘
(4 rows)
=# SELECT * FROM few, ROWS FROM(generate_series(1,3));
┌────┬─────────────────┐
│ id │ generate_series │
├────┼─────────────────┤
│  1 │               1 │
│  2 │               1 │
│  1 │               2 │
│  2 │               2 │
│  1 │               3 │
│  2 │               3 │
└────┴─────────────────┘
(6 rows)
surely isn't what was intended.  So the join order needs to be enforced.

3) tSRFs are evaluated after GROUP BY, and window functions:
=# SELECT generate_series(1, count(*)) FROM (VALUES(1),(2),(10)) f;
┌─────────────────┐
│ generate_series │
├─────────────────┤
│               1 │
│               2 │
│               3 │
└─────────────────┘
which means we have to push the "original" query into a subquery, with
the ROWS FROM laterally referencing the subquery:
SELECT generate_series FROM (SELECT count(*) FROM (VALUES(1),(2),(10)) f) s, ROWS FROM (generate_series(1,s.count));

4) The evaluation order of tSRFs in combination with ORDER BY is a bit  confusing. Namely tSRFs are implemented after
ORDERBY has been  evaluated, unless the ORDER BY references the SRF.
 
E.g.
=# SELECT few.id, generate_series FROM ROWS FROM(generate_series(1,3)),few ORDER BY few.id DESC;
might return
┌────┬─────────────────┐
│ id │ generate_series │
├────┼─────────────────┤
│ 24 │               3 │
│ 24 │               2 │
│ 24 │               1 │
..
instead of
┌────┬─────────────────┐
│ id │ generate_series │
├────┼─────────────────┤
│ 24 │               1 │
│ 24 │               2 │
│ 24 │               3 │
as before.

which means we'll sometimes have to push down the ORDER BY into the
subquery (when not referencing tSRFs, so they're evaluated first),
sometimes evaluate them on the outside (if tSRFs are referenced)

5) tSRFs can have tSRFs as argument, e.g.:
=# SELECT generate_series(1, generate_series(1,3));
┌─────────────────┐
│ generate_series │
├─────────────────┤
│               1 │
│               1 │
│               2 │
│               1 │
│               2 │
│               3 │
└─────────────────┘
that can quite easily be implemented by having the "nested" tSRF
evaluate as a separate ROWS FROM expression.

Which even allows us to implement the previously forbidden
=# SELECT generate_series(generate_series(1,3), generate_series(2,4));
ERROR:  0A000: functions and operators can take at most one set argument

- not that I think that's of great value ;)

6) SETOF record type functions cannot directly be used in ROWS FROM() -  as ROWS FROM "expands" records returned by
functions.When converting  something like
 
CREATE OR REPLACE FUNCTION setof_record_sql() RETURNS SETOF record LANGUAGE sql AS $$SELECT 1 AS a, 2 AS b UNION ALL
SELECT1, 2;$$;
 
SELECT setof_record_sql();  we don't have that available though.

The best way to handle that seems to be to introduce the ability for
ROWS FROM not to expand the record returned by a column.  I'm currently
thinking that something like ROWS FROM(setof_record_sql() AS ()) would
do the trick.  That'd also considerably simplify the handling of
functions returning known composite types - my current POC patch
generates a ROW(a,b,..) type expression for those.

I'm open to better syntax suggestions.

7) ROWS FROM () / functions in the FROM list are currently signifcantly  slower than the equivalent in the target list
(forSFRM_ValuePerCall  SRFs at least):
 

=# COPY (SELECT generate_series(1,10000000)) TO '/dev/null';
COPY 10000000
Time: 1311.469 ms
=# COPY (SELECT * FROM generate_series(1,10000000)) TO '/dev/null';
LOG:  00000: temporary file: path "base/pgsql_tmp/pgsql_tmp702.0", size 140000000
LOCATION:  FileClose, fd.c:1484
COPY 10000000
Time: 2173.282 ms
for SRFM_Materialize SRFs there's no meaningufl difference:
CREATE FUNCTION plpgsql_generate_series(bigint, bigint) RETURNS SETOF bigint LANGUAGE plpgsql AS $$BEGIN RETURN QUERY
SELECTgenerate_series($1, $2);END;$$;
 

=# COPY (SELECT plpgsql_generate_series(1,10000000)) TO '/dev/null';
LOG:  00000: temporary file: path "base/pgsql_tmp/pgsql_tmp702.2", size 180000000
COPY 10000000
Time: 3058.437 ms

=# COPY (SELECT * FROM plpgsql_generate_series(1,10000000)) TO '/dev/null';LOG:  00000: temporary file: path
"base/pgsql_tmp/pgsql_tmp702.1",size 180000000
 
COPY 10000000
Time: 2964.661 ms

that makes sense, because nodeFunctionscan.c, via
ExecMakeTableFunctionResult, forces materialization of ValuePerCall
SRFs.

ISTM that we need should fix that by allowing ValuePerCall without
materialization, as long as EXEC_FLAG_BACKWARD isn't required.


I've implemented ([2]) a prototype of this. My basic approach is:

I) During parse-analysis, remember whether a query has any tSRFs  (Query->hasTargetSRF). That avoids doing a useless
passover the  query, if no tSRFs are present.
 
II) At the beginning of subquery_planner(), before doing any work  operating on subqueries and such, implement SRFs if
->hasTargetSRF(). (unsrfify() in the POC)
 
III) Unconditionally move the "current" query into a subquery. For that  do a mutator pass over the query, replacing
Vars/Aggrefs/...in the  original targetlist with Var references to the new subquery.
(unsrfify_reference_subquery_mutator()in the POC)
 
IV) Do a pass over the outer query's targetlist, and implement any tSRFs  using a ROWS FROM() RTE (or multiple ones in
caseof nested tSRFs).  (unsrfify_implement_srfs_mutator() in the POC)
 

that seems to mostly work well.

The behaviour changes this implies are:

a) Least-common-multiple behaviour, as in (1) above, is gone. I think  that's good.

b) We currently allow tSRFs in UPDATE ... SET expressions. I don't  actually know what that's supposed to mean. I'm
inclined a;
 
=# CREATE TABLE blarg AS SELECT 1::int a;
SELECT 1
=# UPDATE blarg SET a = generate_series(2,3);
UPDATE 1
=# SELECT * FROM blarg ;
┌───┐
│ a │
├───┤
│ 2 │
└───┘
I'm inclined to think that that's a bad idea, and should rather be
forbidden.

c) COALESCE/CASE have, so far, shortcut tSRF expansion. E.g.  SELECT id, COALESCE(1, generate_series(1,2)) FROM
(VALUES(1),(2))few(id);  returns only two rows, despite the generate_series().  But by  implementing the
generate_seriesas a ROWS FROM, it'd return four.
 

I think that's ok.

d) Not a problem with the patch per-se, but I'm doubful that that's ok:
=# SELECT 1 ORDER BY generate_series(1, 10);
returns 10 rows ;) - maybe we should forbid that?

As the patch currently stands, the diffstat is56 files changed, 953 insertions(+), 1599 deletions(-)
which isn't bad. I'd guess that a few more lines are needed, but I'd
still bet it's a net negative code-wise.

Regards,

Andres Freund

[1] http://archives.postgresql.org/message-id/20160801082346.nfp2g7mg74alifdc%40alap3.anarazel.de
[2] http://archives.postgresql.org/message-id/20160804032203.jprhdkx273sqhksd%40alap3.anarazel.de



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
Hi,

On 2016-08-22 16:20:58 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-08-17 17:41:28 -0700, Andres Freund wrote:
> >> Tom, do you think this is roughly going in the right direction?
> 
> I've not had time to look at this patch, I'm afraid.  If you still
> want me to, I can make time in a day or so.

That'd greatly be appreciated. I think polishing the POC up to
committable patch will be a considerable amount of work, and I'd like
design feedback before that.


> > I'm working on these. Atm ExecMakeTableFunctionResult() resides in
> > execQual.c - I'm inlining it into nodeFunctionscan.c now, because
> > there's no other callers, and having it separate seems to bring no
> > benefit.
> 
> > Please speak soon up if you disagree.
> 
> I think ExecMakeTableFunctionResult was placed in execQual.c because
> it seemed to belong there alongside the support for SRFs in tlists.
> If that's going away then there's no good reason not to move the logic
> to where it's used.

Cool, then we agree.

Greetings,

Andres Freund



On 23/08/16 09:40, Andres Freund wrote:
> Hi,
>
> as noted in [1] I started hacking on removing the current implementation
> of SRFs in the targetlist (tSRFs henceforth). IM discussion brought the
> need for a description of the problem, need and approach to light.
>
> There are several reasons for wanting to get rid of tSRFs. The primary
> ones in my opinion are that the current behaviour of several SRFs in one
> targetlist is confusing, and that the implementation burden currently is
> all over the executor.  Especially the latter is what is motivating me
> working on this, because it blocks my work on making the executor faster
> for queries involving significant amounts of tuples.  Batching is hard
> if random places in the querytree can icnrease the number of tuples.
>
> The basic idea, hinted at in several threads, is, at plan time, to convert a query like
> SELECT generate_series(1, 10);
> into
> SELECT generate_series FROM ROWS FROM(generate_series(1, 10));
>
> thereby avoiding the complications in the executor (c.f. execQual.c
> handling of isDone/ExprMultipleResult and supporting code in many
> executor nodes / node->*.ps.ps_TupFromTlist).
>
> There are several design questions along the way:
>
> 1) How to deal with the least-common-multiple behaviour of tSRFs. E.g.
> =# SELECT generate_series(1, 3), generate_series(1,2);
> returning
> ┌─────────────────┬─────────────────┐
> │ generate_series │ generate_series │
> ├─────────────────┼─────────────────┤
> │               1 │               1 │
> │               2 │               2 │
> │               3 │               1 │
> │               1 │               2 │
> │               2 │               1 │
> │               3 │               2 │
> └─────────────────┴─────────────────┘
> (6 rows)
> but
> =# SELECT generate_series(1, 3), generate_series(5,7);
> returning
> ┌─────────────────┬─────────────────┐
> │ generate_series │ generate_series │
> ├─────────────────┼─────────────────┤
> │               1 │               5 │
> │               2 │               6 │
> │               3 │               7 │
> └─────────────────┴─────────────────┘
>
> discussion in this thread came, according to my reading, to the
> conclusion that that behaviour is just confusing and that the ROWS FROM
> behaviour of
> =# SELECT * FROM ROWS FROM(generate_series(1, 3), generate_series(1,2));
> ┌─────────────────┬─────────────────┐
> │ generate_series │ generate_series │
> ├─────────────────┼─────────────────┤
> │               1 │               1 │
> │               2 │               2 │
> │               3 │          (null) │
> └─────────────────┴─────────────────┘
> (3 rows)
>
> makes more sense.
I had always implicitly assumed that having 2 generated sequences would 
act as equivalent to:

SELECT    sa,    sb
FROM    ROWS FROM(generate_series(1, 3)) AS sa,    ROWS FROM(generate_series(5, 7)) AS sb
ORDER BY    sa,    sb;
 sa | sb
----+----  1 |  5  1 |  6  1 |  7  2 |  5  2 |  6  2 |  7  3 |  5  3 |  6  3 |  7


Obviously I was wrong - but to me, my implicit assumption makes more sense!
[...]


Cheers,
Gavin



Re: Changed SRF in targetlist handling

From
Andres Freund
Date:
On 2016-08-17 17:41:28 -0700, Andres Freund wrote:
> a) Allow to avoid using a tuplestore for SRF_PERCALL SRFs in ROWS FROM -
>    otherwise our performance would regress noticeably in some cases.

To demonstrate the problem:

master:
=# COPY (SELECT generate_series(1, 50000000)) TO '/dev/null';
COPY 50000000
Time: 6859.830 ms
=# COPY (SELECT * FROM generate_series(1, 50000000)) TO '/dev/null';
COPY 50000000
Time: 11314.507 ms

getting rid of the materialization indeed fixes the problem:

dev:
=# COPY (SELECT generate_series(1, 50000000)) TO
'/dev/null';
COPY 50000000
Time: 5757.547 ms

=# COPY (SELECT * FROM generate_series(1, 50000000)) TO
'/dev/null';
COPY 50000000
Time: 5842.524 ms

I've currently implemented this by having nodeFunctionscan.c store
enough state in FunctionScanPerFuncState to continue the ValuePerCall
protocol.  That all seems to work well, without big problems.

The open issue here is whether / how we want to deal with
EXEC_FLAG_REWIND and EXEC_FLAG_BACKWARD. Currently that, with some added
complications, is implemented in nodeFunctionscan.c itself. But for
ValuePerCall SRFs that doesn't directly work anymore.

ISTM that the easiest way here is actually to rip out support for
EXEC_FLAG_REWIND/EXEC_FLAG_BACKWARD from nodeFunctionscan.c. If the plan
requires that, the planner will slap a Material node on-top. Which will
even be more efficient when ROWS FROM for multiple SRFs, or WITH
ORDINALITY, are used.  Alternatively we can continue to create a
tuplestore for ValuePerCall when eflags indicates that's required. But
personally I don't see that as an advantageous course.

Comments?

Andres



Hi,

Attached is a significantly updated patch series (see the mail one up
for details about what this is, I don't want to quote it in its
entirety).

There's still some corner cases (DISTINCT + SRF, UNION/INTERSECT with
SRF) to test / implement and a good bit of code cleanup to do. But
feature wise it's pretty much complete.

It currently consists of the following patches:

0001-Add-some-more-targetlist-srf-tests.patch
  Add some test.

0002-Shore-up-some-weird-corner-cases-for-targetlist-SRFs.patch
  Forbid UPDATE ... SET foo = SRF() and ORDER BY / GROUP BY containing
  SRFs that would change the number of returned rows.  Without the
  latter e.g. SELECT 1 ORDER BY generate_series(1,10); returns 10 rows.

0003-Avoid-materializing-SRFs-in-the-FROM-list.patch
  To avoid performance regressions from moving SRFM_ValuePerCall SRFs to
  ROWS FROM, nodeFunctionscan.c needs to support not materializing
  output.

  In my present patch I've *ripped out* the support for materialization
  in nodeFunctionscan.c entirely. That means that rescans referencing
  volatile functions can change their behaviour (if a function is
  rescanned, without having it's parameters changed), and that native
  backward scan support is gone.  I don't think that's actually an issue.

  This temporarily duplicates a bit of code from execQual.c, but that's
  removed again in 0006.

0004-Allow-ROWS-FROM-to-return-functions-as-single-record.patch
  To allow transforming SELECT record_srf(); nodeFunctionscan.c needs to
  learn to return the result as a record.  I chose
  ROWS FROM (record_srf() AS ()) as the syntax for that. It doesn't
  necessarily have to be SQL exposed, but it does make testing easier.

0005-Basic-implementation-of-targetlist-SRFs-via-ROWS-FRO.patch
  Convert all targetlist SRFs to ROWS FROM() expression, referencing the
  original query (without the SRFs) via a subquery.

  Note that this changes the behaviour of queries in a few cases. Namely
  the "least-common-multiple" behaviour of targetlist SRFs is gone, a
  function can now accept multiple set returning functions as input, SRF
  references in COALESCE / CASE are evaluated a bit more "eagerly". The
  last one I have to think a bit more about.

0006-Remove-unused-code-related-to-targetlist-SRFs.patch
  Now that there's no targetlist SRFs at execution time anymore, rip out
  executor and planner code related to that.  There's possibly more, but
  that's what I could find in a couple passes of searching around.

  This actually speeds up tpch-h queries by roughly 4% for me.


My next steps are to work on cleaning up the code a bit more, and
increase the regression coverage.

Input is greatly welcome.

Greetings,

Andres Freund

Attachment
On 2016-08-27 14:48:29 -0700, Andres Freund wrote:
> My next steps are to work on cleaning up the code a bit more, and
> increase the regression coverage.

Oh, there's one open item I actually don't really know how to handle
well: A decent way of enforcing the join order between the subquery and
the functionscan when there's no lateral dependencies.  I've hacked up
the lateral machinery to just always add a pointless dependency, but
that seems fairly ugly.  If somebody has a better idea, that'd be great.

Greetings,

Andres Freund



On 08/28/2016 12:48 AM, Andres Freund wrote:
> Attached is a significantly updated patch series (see the mail one up
> for details about what this is, I don't want to quote it in its
> entirety).
>
> There's still some corner cases (DISTINCT + SRF, UNION/INTERSECT with
> SRF) to test / implement and a good bit of code cleanup to do. But
> feature wise it's pretty much complete.

Looks good, aside from the few FIXMEs, TODOs and XXXs and DIRTYs.

I think we need to come up with a better word for "unsrfify". That's 
quite a mouthful. Perhaps something as boring as 
"convert_srfs_to_function_rtes".

Would it make sense for addRangeTableEntryForFunction() to take  a List 
of RangeFunctionElems as argument, now that we have such a struct? The 
lists-of-same-length approach gets a bit tedious.

Typos:
s/fortfour/forfour
s/Each element of this list a/ Each element of this list is a/

- Heikki




On 2016-08-29 12:56:25 +0300, Heikki Linnakangas wrote:
> On 08/28/2016 12:48 AM, Andres Freund wrote:
> > Attached is a significantly updated patch series (see the mail one up
> > for details about what this is, I don't want to quote it in its
> > entirety).
> > 
> > There's still some corner cases (DISTINCT + SRF, UNION/INTERSECT with
> > SRF) to test / implement and a good bit of code cleanup to do. But
> > feature wise it's pretty much complete.
> 
> Looks good

Thanks for the look!


> aside from the few FIXMEs, TODOs and XXXs

Those I pretty much know to handle.


> DIRTYs.

But I think this one is the "ordering" dependency information, and there
I don't yet have good idea.


> I think we need to come up with a better word for "unsrfify". That's quite a
> mouthful. Perhaps something as boring as "convert_srfs_to_function_rtes".

Yea, that was more of a working title. Maybe implement_targetlist_srfs()?


> Would it make sense for addRangeTableEntryForFunction() to take  a List of
> RangeFunctionElems as argument, now that we have such a struct? The
> lists-of-same-length approach gets a bit tedious.

Yea, I was thinking the same.


> Typos:
> s/fortfour/forfour
> s/Each element of this list a/ Each element of this list is a/

Thanks.

Greetings,

Andres Freund



On Tue, Aug 23, 2016 at 3:10 AM, Andres Freund <andres@anarazel.de> wrote:
> as noted in [1] I started hacking on removing the current implementation
> of SRFs in the targetlist (tSRFs henceforth). IM discussion brought the
> need for a description of the problem, need and approach to light.

Thanks for writing this up.

> 1) How to deal with the least-common-multiple behaviour of tSRFs. E.g.
> =# SELECT generate_series(1, 3), generate_series(1,2);
> returning
> ┌─────────────────┬─────────────────┐
> │ generate_series │ generate_series │
> ├─────────────────┼─────────────────┤
> │               1 │               1 │
> │               2 │               2 │
> │               3 │               1 │
> │               1 │               2 │
> │               2 │               1 │
> │               3 │               2 │
> └─────────────────┴─────────────────┘
> (6 rows)
> but
> =# SELECT generate_series(1, 3), generate_series(5,7);
> returning
> ┌─────────────────┬─────────────────┐
> │ generate_series │ generate_series │
> ├─────────────────┼─────────────────┤
> │               1 │               5 │
> │               2 │               6 │
> │               3 │               7 │
> └─────────────────┴─────────────────┘
>
> discussion in this thread came, according to my reading, to the
> conclusion that that behaviour is just confusing and that the ROWS FROM
> behaviour of
> =# SELECT * FROM ROWS FROM(generate_series(1, 3), generate_series(1,2));
> ┌─────────────────┬─────────────────┐
> │ generate_series │ generate_series │
> ├─────────────────┼─────────────────┤
> │               1 │               1 │
> │               2 │               2 │
> │               3 │          (null) │
> └─────────────────┴─────────────────┘
> (3 rows)
>
> makes more sense.  We also discussed erroring out if two SRFs  return
> differing amount of rows, but that seems not to be preferred so far. And
> we can easily add it if we want.

This all seems fine.  I don't think erroring out is an improvement.

> 2) A naive conversion to ROWS FROM, like in the example in the
>    introductory paragraph, can change the output, when implemented as a
>    join from ROWS FROM to the rest of the query, rather than the other
>    way round. E.g.
> =# EXPLAIN SELECT * FROM few, ROWS FROM(generate_series(1,10));
> ┌──────────────────────────────────────────────────────────────────────────────┐
> │                                  QUERY PLAN                                  │
> ├──────────────────────────────────────────────────────────────────────────────┤
> │ Nested Loop  (cost=0.00..36.03 rows=2000 width=8)                            │
> │   ->  Function Scan on generate_series  (cost=0.00..10.00 rows=1000 width=4) │
> │   ->  Materialize  (cost=0.00..1.03 rows=2 width=4)                          │
> │         ->  Seq Scan on few  (cost=0.00..1.02 rows=2 width=4)                │
> └──────────────────────────────────────────────────────────────────────────────┘
> (4 rows)
> =# SELECT * FROM few, ROWS FROM(generate_series(1,3));
> ┌────┬─────────────────┐
> │ id │ generate_series │
> ├────┼─────────────────┤
> │  1 │               1 │
> │  2 │               1 │
> │  1 │               2 │
> │  2 │               2 │
> │  1 │               3 │
> │  2 │               3 │
> └────┴─────────────────┘
> (6 rows)
> surely isn't what was intended.  So the join order needs to be enforced.

In general, we've been skeptical about giving any guarantees about
result ordering.  Maybe this case is different and we should give some
guarantee here, but I don't think it's 100% obvious.

> 3) tSRFs are evaluated after GROUP BY, and window functions:
> =# SELECT generate_series(1, count(*)) FROM (VALUES(1),(2),(10)) f;
> ┌─────────────────┐
> │ generate_series │
> ├─────────────────┤
> │               1 │
> │               2 │
> │               3 │
> └─────────────────┘
> which means we have to push the "original" query into a subquery, with
> the ROWS FROM laterally referencing the subquery:
> SELECT generate_series FROM (SELECT count(*) FROM (VALUES(1),(2),(10)) f) s, ROWS FROM (generate_series(1,s.count));

Seems OK.

> 4) The evaluation order of tSRFs in combination with ORDER BY is a bit
>    confusing. Namely tSRFs are implemented after ORDER BY has been
>    evaluated, unless the ORDER BY references the SRF.
> E.g.
> =# SELECT few.id, generate_series FROM ROWS FROM(generate_series(1,3)),few ORDER BY few.id DESC;
> might return
> ┌────┬─────────────────┐
> │ id │ generate_series │
> ├────┼─────────────────┤
> │ 24 │               3 │
> │ 24 │               2 │
> │ 24 │               1 │
> ..
> instead of
> ┌────┬─────────────────┐
> │ id │ generate_series │
> ├────┼─────────────────┤
> │ 24 │               1 │
> │ 24 │               2 │
> │ 24 │               3 │
> as before.
>
> which means we'll sometimes have to push down the ORDER BY into the
> subquery (when not referencing tSRFs, so they're evaluated first),
> sometimes evaluate them on the outside (if tSRFs are referenced)

OK.

> 5) tSRFs can have tSRFs as argument, e.g.:
> =# SELECT generate_series(1, generate_series(1,3));
> ┌─────────────────┐
> │ generate_series │
> ├─────────────────┤
> │               1 │
> │               1 │
> │               2 │
> │               1 │
> │               2 │
> │               3 │
> └─────────────────┘
> that can quite easily be implemented by having the "nested" tSRF
> evaluate as a separate ROWS FROM expression.
>
> Which even allows us to implement the previously forbidden
> =# SELECT generate_series(generate_series(1,3), generate_series(2,4));
> ERROR:  0A000: functions and operators can take at most one set argument
>
> - not that I think that's of great value ;)

OK.

> 6) SETOF record type functions cannot directly be used in ROWS FROM() -
>    as ROWS FROM "expands" records returned by functions. When converting
>    something like
> CREATE OR REPLACE FUNCTION setof_record_sql() RETURNS SETOF record LANGUAGE sql AS $$SELECT 1 AS a, 2 AS b UNION ALL
SELECT1, 2;$$;
 
> SELECT setof_record_sql();
>    we don't have that available though.
>
> The best way to handle that seems to be to introduce the ability for
> ROWS FROM not to expand the record returned by a column.  I'm currently
> thinking that something like ROWS FROM(setof_record_sql() AS ()) would
> do the trick.  That'd also considerably simplify the handling of
> functions returning known composite types - my current POC patch
> generates a ROW(a,b,..) type expression for those.
>
> I'm open to better syntax suggestions.

I definitely agree that having some syntax to avoid row-expansion in
this case (and maybe in other cases) would be a good thing; I suspect
that would get a good bit of use.  I don't care much for that
particular choice of syntax, which seems fairly magical, but I'm not
sure what would be better.

> 7) ROWS FROM () / functions in the FROM list are currently signifcantly
>    slower than the equivalent in the target list (for SFRM_ValuePerCall
>    SRFs at least):
>
> =# COPY (SELECT generate_series(1,10000000)) TO '/dev/null';
> COPY 10000000
> Time: 1311.469 ms
> =# COPY (SELECT * FROM generate_series(1,10000000)) TO '/dev/null';
> LOG:  00000: temporary file: path "base/pgsql_tmp/pgsql_tmp702.0", size 140000000
> LOCATION:  FileClose, fd.c:1484
> COPY 10000000
> Time: 2173.282 ms
> for SRFM_Materialize SRFs there's no meaningufl difference:
> CREATE FUNCTION plpgsql_generate_series(bigint, bigint) RETURNS SETOF bigint LANGUAGE plpgsql AS $$BEGIN RETURN QUERY
SELECTgenerate_series($1, $2);END;$$;
 
>
> =# COPY (SELECT plpgsql_generate_series(1,10000000)) TO '/dev/null';
> LOG:  00000: temporary file: path "base/pgsql_tmp/pgsql_tmp702.2", size 180000000
> COPY 10000000
> Time: 3058.437 ms
>
> =# COPY (SELECT * FROM plpgsql_generate_series(1,10000000)) TO '/dev/null';LOG:  00000: temporary file: path
"base/pgsql_tmp/pgsql_tmp702.1",size 180000000
 
> COPY 10000000
> Time: 2964.661 ms
>
> that makes sense, because nodeFunctionscan.c, via
> ExecMakeTableFunctionResult, forces materialization of ValuePerCall
> SRFs.
>
> ISTM that we need should fix that by allowing ValuePerCall without
> materialization, as long as EXEC_FLAG_BACKWARD isn't required.

That sounds good.
> I've implemented ([2]) a prototype of this. My basic approach is:
>
> I) During parse-analysis, remember whether a query has any tSRFs
>    (Query->hasTargetSRF). That avoids doing a useless pass over the
>    query, if no tSRFs are present.
> II) At the beginning of subquery_planner(), before doing any work
>    operating on subqueries and such, implement SRFs if ->hasTargetSRF().
>    (unsrfify() in the POC)
> III) Unconditionally move the "current" query into a subquery. For that
>    do a mutator pass over the query, replacing Vars/Aggrefs/... in the
>    original targetlist with Var references to the new subquery.
>    (unsrfify_reference_subquery_mutator() in the POC)
> IV) Do a pass over the outer query's targetlist, and implement any tSRFs
>    using a ROWS FROM() RTE (or multiple ones in case of nested tSRFs).
>    (unsrfify_implement_srfs_mutator() in the POC)
>
> that seems to mostly work well.

I gather that III and IV are skipped if hasTargetSRF isn't set.

> The behaviour changes this implies are:
>
> a) Least-common-multiple behaviour, as in (1) above, is gone. I think
>    that's good.
>
> b) We currently allow tSRFs in UPDATE ... SET expressions. I don't
>    actually know what that's supposed to mean. I'm inclined
>    a;
> =# CREATE TABLE blarg AS SELECT 1::int a;
> SELECT 1
> =# UPDATE blarg SET a = generate_series(2,3);
> UPDATE 1
> =# SELECT * FROM blarg ;
> ┌───┐
> │ a │
> ├───┤
> │ 2 │
> └───┘
> I'm inclined to think that that's a bad idea, and should rather be
> forbidden.
>
> c) COALESCE/CASE have, so far, shortcut tSRF expansion. E.g.
>    SELECT id, COALESCE(1, generate_series(1,2)) FROM (VALUES(1),(2)) few(id);
>    returns only two rows, despite the generate_series().  But by
>    implementing the generate_series as a ROWS FROM, it'd return four.
>
> I think that's ok.

Those all sound OK.

> d) Not a problem with the patch per-se, but I'm doubful that that's ok:
> =# SELECT 1 ORDER BY generate_series(1, 10);
> returns 10 rows ;) - maybe we should forbid that?

OK by me.  I feel like this isn't the only case where the presence of
resjunk columns has user-visible effects, although I can't think of
another one right at the moment.  It seems like something to avoid,
though.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

On Sun, Aug 28, 2016 at 3:18 AM, Andres Freund <andres@anarazel.de> wrote:
> 0003-Avoid-materializing-SRFs-in-the-FROM-list.patch
>   To avoid performance regressions from moving SRFM_ValuePerCall SRFs to
>   ROWS FROM, nodeFunctionscan.c needs to support not materializing
>   output.
>
>   In my present patch I've *ripped out* the support for materialization
>   in nodeFunctionscan.c entirely. That means that rescans referencing
>   volatile functions can change their behaviour (if a function is
>   rescanned, without having it's parameters changed), and that native
>   backward scan support is gone.  I don't think that's actually an issue.

Can you expand on why you think those things aren't an issue?  Because
it seems like they might be.

> 0006-Remove-unused-code-related-to-targetlist-SRFs.patch
>   Now that there's no targetlist SRFs at execution time anymore, rip out
>   executor and planner code related to that.  There's possibly more, but
>   that's what I could find in a couple passes of searching around.
>
>   This actually speeds up tpch-h queries by roughly 4% for me.

Nice.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



On Fri, Sep 2, 2016 at 3:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Tue, Aug 23, 2016 at 3:10 AM, Andres Freund <andres@anarazel.de> wrote:

>> =# SELECT * FROM few, ROWS FROM(generate_series(1,3));
>> ┌────┬─────────────────┐
>> │ id │ generate_series │
>> ├────┼─────────────────┤
>> │  1 │               1 │
>> │  2 │               1 │
>> │  1 │               2 │
>> │  2 │               2 │
>> │  1 │               3 │
>> │  2 │               3 │
>> └────┴─────────────────┘
>> (6 rows)
>> surely isn't what was intended.  So the join order needs to be enforced.
>
> In general, we've been skeptical about giving any guarantees about
> result ordering.

+1

I think it is a very bad idea to move away from the statement that
a query generates a set of rows, and that no order is guaranteed
unless the top level has an ORDER BY clause.  How hard is it to add
ORDER BY 1, 2 to the above query?  Let the optimizer notice when a
node returns data in the needed order and skip the sort if possible.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

On 2016-09-02 09:05:35 -0500, Kevin Grittner wrote:
> On Fri, Sep 2, 2016 at 3:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> > On Tue, Aug 23, 2016 at 3:10 AM, Andres Freund <andres@anarazel.de> wrote:
> 
> >> =# SELECT * FROM few, ROWS FROM(generate_series(1,3));
> >> ┌────┬─────────────────┐
> >> │ id │ generate_series │
> >> ├────┼─────────────────┤
> >> │  1 │               1 │
> >> │  2 │               1 │
> >> │  1 │               2 │
> >> │  2 │               2 │
> >> │  1 │               3 │
> >> │  2 │               3 │
> >> └────┴─────────────────┘
> >> (6 rows)
> >> surely isn't what was intended.  So the join order needs to be enforced.
> >
> > In general, we've been skeptical about giving any guarantees about
> > result ordering.

Well, it's historically how we behaved for SRFs. I'm pretty sure that
people will be confused if
SELECT generate_series(1, 10) FROM sometbl;
suddenly returns rows in an order that reverse from what
generate_series() returns.

> +
> 
> I think it is a very bad idea to move away from the statement that
> a query generates a set of rows, and that no order is guaranteed
> unless the top level has an ORDER BY clause.  How hard is it to add
> ORDER BY 1, 2 to the above query?  Let the optimizer notice when a
> node returns data in the needed order and skip the sort if possible.

There's no such infrastructure for SRFS/ROWS FROM.

Andres



Hi,

Thanks for looking.

On 2016-09-02 14:01:32 +0530, Robert Haas wrote:
> > 6) SETOF record type functions cannot directly be used in ROWS FROM() -
> >    as ROWS FROM "expands" records returned by functions. When converting
> >    something like
> > CREATE OR REPLACE FUNCTION setof_record_sql() RETURNS SETOF record LANGUAGE sql AS $$SELECT 1 AS a, 2 AS b UNION
ALLSELECT 1, 2;$$;
 
> > SELECT setof_record_sql();
> >    we don't have that available though.
> >
> > The best way to handle that seems to be to introduce the ability for
> > ROWS FROM not to expand the record returned by a column.  I'm currently
> > thinking that something like ROWS FROM(setof_record_sql() AS ()) would
> > do the trick.  That'd also considerably simplify the handling of
> > functions returning known composite types - my current POC patch
> > generates a ROW(a,b,..) type expression for those.
> >
> > I'm open to better syntax suggestions.
> 
> I definitely agree that having some syntax to avoid row-expansion in
> this case (and maybe in other cases) would be a good thing; I suspect
> that would get a good bit of use.  I don't care much for that
> particular choice of syntax, which seems fairly magical, but I'm not
> sure what would be better.

I'm not a fan either, but until somebody ocmes up with something better
:/

> That sounds good.
> > I've implemented ([2]) a prototype of this. My basic approach is:
> >
> > I) During parse-analysis, remember whether a query has any tSRFs
> >    (Query->hasTargetSRF). That avoids doing a useless pass over the
> >    query, if no tSRFs are present.
> > II) At the beginning of subquery_planner(), before doing any work
> >    operating on subqueries and such, implement SRFs if ->hasTargetSRF().
> >    (unsrfify() in the POC)
> > III) Unconditionally move the "current" query into a subquery. For that
> >    do a mutator pass over the query, replacing Vars/Aggrefs/... in the
> >    original targetlist with Var references to the new subquery.
> >    (unsrfify_reference_subquery_mutator() in the POC)
> > IV) Do a pass over the outer query's targetlist, and implement any tSRFs
> >    using a ROWS FROM() RTE (or multiple ones in case of nested tSRFs).
> >    (unsrfify_implement_srfs_mutator() in the POC)
> >
> > that seems to mostly work well.
> 
> I gather that III and IV are skipped if hasTargetSRF isn't set.

Precisely.


> > d) Not a problem with the patch per-se, but I'm doubful that that's ok:
> > =# SELECT 1 ORDER BY generate_series(1, 10);
> > returns 10 rows ;) - maybe we should forbid that?
> 
> OK by me.  I feel like this isn't the only case where the presence of
> resjunk columns has user-visible effects, although I can't think of
> another one right at the moment.  It seems like something to avoid,
> though.

An early patch in the series now errors out if ORDER BY or GROUP BY adds
a retset resjunk element.


Regards,

Andres



On Fri, Sep 2, 2016 at 9:11 AM, Andres Freund <andres@anarazel.de> wrote:
> On 2016-09-02 09:05:35 -0500, Kevin Grittner wrote:
>> On Fri, Sep 2, 2016 at 3:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>>> On Tue, Aug 23, 2016 at 3:10 AM, Andres Freund <andres@anarazel.de> wrote:
>>
>>>> =# SELECT * FROM few, ROWS FROM(generate_series(1,3));
>>>> ┌────┬─────────────────┐
>>>> │ id │ generate_series │
>>>> ├────┼─────────────────┤
>>>> │  1 │               1 │
>>>> │  2 │               1 │
>>>> │  1 │               2 │
>>>> │  2 │               2 │
>>>> │  1 │               3 │
>>>> │  2 │               3 │
>>>> └────┴─────────────────┘
>>>> (6 rows)
>>>> surely isn't what was intended.  So the join order needs to be enforced.
>>>
>>> In general, we've been skeptical about giving any guarantees about
>>> result ordering.
>
> Well, it's historically how we behaved for SRFs.

And until we had synchronized scans a sequential scan always
returned rows in the order they were present in the heap.
Implementation details are not guarantees.

> I'm pretty sure that people will be confused if
> SELECT generate_series(1, 10) FROM sometbl;
> suddenly returns rows in an order that reverse from what
> generate_series() returns.

If this changes, it is probably worth a mentioning in the release
notes.

>> I think it is a very bad idea to move away from the statement that
>> a query generates a set of rows, and that no order is guaranteed
>> unless the top level has an ORDER BY clause.  How hard is it to add
>> ORDER BY 1, 2 to the above query?  Let the optimizer notice when a
>> node returns data in the needed order and skip the sort if possible.
>
> There's no such infrastructure for SRFS/ROWS FROM.

Well, that's something to fix (or not), but not a justification for
"except on Tuesdays when the moon is full" sorts of exceptions to
simple rules about what to expect.  No ORDER BY means no order
guaranteed.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



On 2016-09-02 07:11:10 -0700, Andres Freund wrote:
> On 2016-09-02 09:05:35 -0500, Kevin Grittner wrote:
> > On Fri, Sep 2, 2016 at 3:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> > > On Tue, Aug 23, 2016 at 3:10 AM, Andres Freund <andres@anarazel.de> wrote:
> > 
> > >> =# SELECT * FROM few, ROWS FROM(generate_series(1,3));
> > >> ┌────┬─────────────────┐
> > >> │ id │ generate_series │
> > >> ├────┼─────────────────┤
> > >> │  1 │               1 │
> > >> │  2 │               1 │
> > >> │  1 │               2 │
> > >> │  2 │               2 │
> > >> │  1 │               3 │
> > >> │  2 │               3 │
> > >> └────┴─────────────────┘
> > >> (6 rows)
> > >> surely isn't what was intended.  So the join order needs to be enforced.
> > >
> > > In general, we've been skeptical about giving any guarantees about
> > > result ordering.
> 
> Well, it's historically how we behaved for SRFs. I'm pretty sure that
> people will be confused if
> SELECT generate_series(1, 10) FROM sometbl;
> suddenly returns rows in an order that reverse from what
> generate_series() returns.

Oh, and we've previously re-added that based on
complaints. C.f. d543170f2fdd6d9845aaf91dc0f6be7a2bf0d9e7 (and others
IIRC).



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-02 09:05:35 -0500, Kevin Grittner wrote:
>>> In general, we've been skeptical about giving any guarantees about
>>> result ordering.

> Well, it's historically how we behaved for SRFs. I'm pretty sure that
> people will be confused if
> SELECT generate_series(1, 10) FROM sometbl;
> suddenly returns rows in an order that reverse from what
> generate_series() returns.

True, but how much "enforcement" do we need really?  This will be a cross
product join, which means that it can only be done as a nestloop not as a
merge or hash (there being no join key to merge or hash on).  ISTM all we
need is that the SRF be on the inside of the join, which is automatic
if it's LATERAL.

>> I think it is a very bad idea to move away from the statement that
>> a query generates a set of rows, and that no order is guaranteed
>> unless the top level has an ORDER BY clause.  How hard is it to add
>> ORDER BY 1, 2 to the above query?  Let the optimizer notice when a
>> node returns data in the needed order and skip the sort if possible.

> There's no such infrastructure for SRFS/ROWS FROM.

And in particular nothing to ORDER BY in this example.
        regards, tom lane



Andres Freund <andres@anarazel.de> writes:
> Oh, and we've previously re-added that based on
> complaints. C.f. d543170f2fdd6d9845aaf91dc0f6be7a2bf0d9e7 (and others
> IIRC).

That one wasn't about row order per se, but I agree that people *will*
bitch if we change the behavior, especially if we don't provide a way
to fix it.  ORDER BY is not a useful suggestion when there is nothing
you could order by to get the old behavior.
        regards, tom lane



On 2016-09-02 10:20:42 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-02 09:05:35 -0500, Kevin Grittner wrote:
> >>> In general, we've been skeptical about giving any guarantees about
> >>> result ordering.
> 
> > Well, it's historically how we behaved for SRFs. I'm pretty sure that
> > people will be confused if
> > SELECT generate_series(1, 10) FROM sometbl;
> > suddenly returns rows in an order that reverse from what
> > generate_series() returns.
> 
> True, but how much "enforcement" do we need really?  This will be a cross
> product join, which means that it can only be done as a nestloop not as a
> merge or hash (there being no join key to merge or hash on).  ISTM all we
> need is that the SRF be on the inside of the join, which is automatic
> if it's LATERAL.

Right. But there's nothing to force a lateral reference to be there
intrinsically. I've added a "fake" lateral reference to the ROWS FROM
RTE to the subquery, when there's none otherwise, but that's not
entirely pretty.  I'm inclined to go with that though, unless somebody
has a better idea.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-02 10:20:42 -0400, Tom Lane wrote:
>> ... ISTM all we
>> need is that the SRF be on the inside of the join, which is automatic
>> if it's LATERAL.

> Right. But there's nothing to force a lateral reference to be there
> intrinsically. I've added a "fake" lateral reference to the ROWS FROM
> RTE to the subquery, when there's none otherwise, but that's not
> entirely pretty.

Hm, do you get cases like this right:
select generate_series(1, t1.a) from t1, t2;

That would result in a lateral ref from the SRF RTE to t1, but you really
need to treat it as laterally dependent on the join of t1/t2 in order to
preserve the old semantics.  That is, you need to be laterally dependent
on the whole FROM clause regardless of which variable references appear.
        regards, tom lane



On Fri, Sep 2, 2016 at 9:25 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> Oh, and we've previously re-added that based on
>> complaints. C.f. d543170f2fdd6d9845aaf91dc0f6be7a2bf0d9e7 (and others
>> IIRC).
>
> That one wasn't about row order per se, but I agree that people *will*
> bitch if we change the behavior, especially if we don't provide a way
> to fix it.

They might also bitch if you add any overhead to put rows in a
specific order when they subsequently sort the rows into some
different order.  You might even destroy an order that would have
allowed a sort step to be skipped, so you would pay twice -- once
to put them into some "implied" order and then to sort them back
into the order they would have had without that extra effort.

> ORDER BY is not a useful suggestion when there is nothing
> you could order by to get the old behavior.

I'm apparently missing something, because I see a column with the
header "generate_series" in the result set.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Kevin Grittner <kgrittn@gmail.com> writes:
> On Fri, Sep 2, 2016 at 9:25 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> ORDER BY is not a useful suggestion when there is nothing
>> you could order by to get the old behavior.

> I'm apparently missing something, because I see a column with the
> header "generate_series" in the result set.

You are apparently only thinking about generate_series and not any
other SRF.  Other SRFs don't necessarily produce outputs that are
in a nice sortable order.  Even for one that does, sorting by it
would destroy the existing behavior:

regression=# select *, generate_series(1,3) from int8_tbl;       q1        |        q2         | generate_series 
------------------+-------------------+-----------------             123 |               456 |               1
  123 |               456 |               2             123 |               456 |               3             123 |
4567890123456789|               1             123 |  4567890123456789 |               2             123 |
4567890123456789|               34567890123456789 |               123 |               14567890123456789 |
123|               24567890123456789 |               123 |               34567890123456789 |  4567890123456789 |
      14567890123456789 |  4567890123456789 |               24567890123456789 |  4567890123456789 |
34567890123456789| -4567890123456789 |               14567890123456789 | -4567890123456789 |
24567890123456789| -4567890123456789 |               3
 
(15 rows)

Now you could argue that the ordering of the table rows
themselves is poorly defined, and you'd be right, but that
doesn't change the fact that the generate_series output
has a well-defined repeating sequence.  People might be
relying on that property.
        regards, tom lane



On Fri, Sep 2, 2016 at 9:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> regression=# select *, generate_series(1,3) from int8_tbl;

I'm sure that you realize that running a query of that form twice
against a table with more than one heap page could result in rows
in a different order, even if no changes had been made to the
database (including no vacuum activity, auto- or otherwise).  If
someone reported that as a bug, what would we tell them?

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Kevin Grittner <kgrittn@gmail.com> writes:
> On Fri, Sep 2, 2016 at 9:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> regression=# select *, generate_series(1,3) from int8_tbl;

> I'm sure that you realize that running a query of that form twice
> against a table with more than one heap page could result in rows
> in a different order, even if no changes had been made to the
> database (including no vacuum activity, auto- or otherwise).

You missed my point: they might complain about the generate_series
output not being in the order they expect, independently of what
the table rows are.

Also, before getting too high and mighty with users who expect
"select * from table" to produce rows in a predictable order,
you should reflect on the number of places in our regression
tests that assume exactly that ...
        regards, tom lane



On 2016-09-02 10:34:54 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-02 10:20:42 -0400, Tom Lane wrote:
> >> ... ISTM all we
> >> need is that the SRF be on the inside of the join, which is automatic
> >> if it's LATERAL.
> 
> > Right. But there's nothing to force a lateral reference to be there
> > intrinsically. I've added a "fake" lateral reference to the ROWS FROM
> > RTE to the subquery, when there's none otherwise, but that's not
> > entirely pretty.
> 
> Hm, do you get cases like this right:
> 
>     select generate_series(1, t1.a) from t1, t2;
> 
> That would result in a lateral ref from the SRF RTE to t1, but you really
> need to treat it as laterally dependent on the join of t1/t2 in order to
> preserve the old semantics.  That is, you need to be laterally dependent
> on the whole FROM clause regardless of which variable references appear.

Yes - as the original query is moved into a subquery, the lateral
dependency I force-add simply is to the entire subquery atm (as a
wholerow var).



On 2016-09-02 09:41:28 -0500, Kevin Grittner wrote:
> On Fri, Sep 2, 2016 at 9:25 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Andres Freund <andres@anarazel.de> writes:
> >> Oh, and we've previously re-added that based on
> >> complaints. C.f. d543170f2fdd6d9845aaf91dc0f6be7a2bf0d9e7 (and others
> >> IIRC).
> >
> > That one wasn't about row order per se, but I agree that people *will*
> > bitch if we change the behavior, especially if we don't provide a way
> > to fix it.
> 
> They might also bitch if you add any overhead to put rows in a
> specific order when they subsequently sort the rows into some
> different order.

Huh? It's just the order the SRFs are returning rows. If they
subsequently ORDER, there's no issue. And that doesn't have a
performance impact afaict.


> You might even destroy an order that would have
> allowed a sort step to be skipped, so you would pay twice -- once
> to put them into some "implied" order and then to sort them back
> into the order they would have had without that extra effort.

So you're arguing that you can't rely on order, but that users rely on
order?

Greetings,

Andres Freund



On Fri, Sep 2, 2016 at 10:31 AM, Andres Freund <andres@anarazel.de> wrote:
> On 2016-09-02 09:41:28 -0500, Kevin Grittner wrote:
>> On Fri, Sep 2, 2016 at 9:25 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> > Andres Freund <andres@anarazel.de> writes:
>> >> Oh, and we've previously re-added that based on
>> >> complaints. C.f. d543170f2fdd6d9845aaf91dc0f6be7a2bf0d9e7 (and others
>> >> IIRC).
>> >
>> > That one wasn't about row order per se, but I agree that people *will*
>> > bitch if we change the behavior, especially if we don't provide a way
>> > to fix it.
>>
>> They might also bitch if you add any overhead to put rows in a
>> specific order when they subsequently sort the rows into some
>> different order.
>
> Huh? It's just the order the SRFs are returning rows. If they
> subsequently ORDER, there's no issue. And that doesn't have a
> performance impact afaict.

If it has no significant performance impact to maintain the
historical order, then I have no problem with doing so.  If you
burn resources putting them into historical order, that is going to
be completely wasted effort in some queries.  THAT is what I would
object to.  I'm certainly not arguing that we have any reason to go
out of our way to change the order.

>> You might even destroy an order that would have
>> allowed a sort step to be skipped, so you would pay twice -- once
>> to put them into some "implied" order and then to sort them back
>> into the order they would have had without that extra effort.
>
> So you're arguing that you can't rely on order, but that users rely on
> order?

No.  I'm arguing that we track the order coming out of different
nodes during planning, and sometimes take advantage of it to avoid
a sort which would otherwise be required.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



On Fri, Sep 2, 2016 at 10:10 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Also, before getting too high and mighty with users who expect
> "select * from table" to produce rows in a predictable order,
> you should reflect on the number of places in our regression
> tests that assume exactly that ...

An assumption that not infrequently breaks.  AFAIK, we generally
adjust the tests when that happens, rather than considering it a
bug in the code.  I never thought we did that because there was a
secret, undocumented guarantee of order, but to allow different
code paths to be tested than we would test if we always specified
an order.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



On Fri, Sep 2, 2016 at 10:10 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Kevin Grittner <kgrittn@gmail.com> writes:
>> On Fri, Sep 2, 2016 at 9:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> regression=# select *, generate_series(1,3) from int8_tbl;
>
>> I'm sure that you realize that running a query of that form twice
>> against a table with more than one heap page could result in rows
>> in a different order, even if no changes had been made to the
>> database (including no vacuum activity, auto- or otherwise).
>
> You missed my point: they might complain about the generate_series
> output not being in the order they expect, independently of what
> the table rows are.

I didn't miss it, I just never thought that anyone would care about
a secondary sort key if the primary sort key was random.  I have
trouble imagining a use-case for that.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



On 2016-09-02 10:58:59 -0500, Kevin Grittner wrote:
> If it has no significant performance impact to maintain the
> historical order, then I have no problem with doing so.

It's not really a runtime issue, it's just a question of how to nicely
constraint the join order. There's no additional sorting or such.


> No.  I'm arguing that we track the order coming out of different
> nodes during planning, and sometimes take advantage of it to avoid
> a sort which would otherwise be required.

I don't think that's realistically possible with SRFs, given they're
often in some language which we have no insight on from the planner
point of view. We could possibly hack something up for SQL SRFs (that'd
be nice, but I'm doubtful it's worth it), but for everything else it
seems unrealistic.  What we could do is to add efficient
ROWS FROM (..) WITH ORDINALITY ORDER bY ordinality;
support.

Andres



Andres Freund <andres@anarazel.de> writes:
> ...  What we could do is to add efficient
> ROWS FROM (..) WITH ORDINALITY ORDER bY ordinality;
> support.

Hm?

regression=# explain select * from rows from (generate_series(1,10)) with ordinality order by ordinality;
              QUERY PLAN                                
 
-------------------------------------------------------------------------Function Scan on generate_series
(cost=0.00..10.00rows=1000 width=12)
 
(1 row)
        regards, tom lane



On 2016-09-02 14:04:24 +0530, Robert Haas wrote:
> On Sun, Aug 28, 2016 at 3:18 AM, Andres Freund <andres@anarazel.de> wrote:
> > 0003-Avoid-materializing-SRFs-in-the-FROM-list.patch
> >   To avoid performance regressions from moving SRFM_ValuePerCall SRFs to
> >   ROWS FROM, nodeFunctionscan.c needs to support not materializing
> >   output.
> >
> >   In my present patch I've *ripped out* the support for materialization
> >   in nodeFunctionscan.c entirely. That means that rescans referencing
> >   volatile functions can change their behaviour (if a function is
> >   rescanned, without having it's parameters changed), and that native
> >   backward scan support is gone.  I don't think that's actually an issue.
> 
> Can you expand on why you think those things aren't an issue?  Because
> it seems like they might be.

Backward scans are, by the planner, easily implemented by adding a
materialize node. Which will, when ordinality or multiple ROWS FROM
expressions are present, even be more runtime & memory efficient.  I
also don't think all that many people use FOR SCROLL cursors over SRFs
containing queries.

The part about rewinding is a bit more complicated. As of HEAD, a
rewound scan where some of the SRFs have to change due to parameter
inputs, but others don't, will only re-compute the ones with parameter
changes.  I don't think it's more confusing to rescan the entire input,
rather parts of it in that case.  If the entire input is re-scanned, the
planner knows how to materialize the entire scan output.

I think it'd be pretty annoying to continue to always materialize
ValuePerCall SRFs just to support that type of re-scan behaviour. We
don't really, to my knowledge, flag well whether rescans are required
atm, so we can't even easily do it conditionally.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> Attached is a significantly updated patch series (see the mail one up
> for details about what this is, I don't want to quote it in its
> entirety).

I've finally cleared my plate enough to start reviewing this patchset.

> 0001-Add-some-more-targetlist-srf-tests.patch
>   Add some test.

I think you should go ahead and push this tests-adding patch now, as it
adds documentation of the current behavior that is a good thing to have
independently of what the rest of the patchset does.  Also, I'm worried
that some of the GROUP BY tests might have machine-dependent results
(if they are implemented by hashing) so it would be good to get in a few
buildfarm cycles and let that settle out before we start changing the
answers.

I do have some trivial nitpicks about 0001:
# rules cannot run concurrently with any test that creates a view
-test: rules psql_crosstab amutils
+test: rules psql_crosstab amutils tsrf

Although tsrf.sql doesn't currently need to create any views, it doesn't
seem like a great idea to assume that it never will.  Maybe add this
after misc_functions in the previous parallel group, instead?

+-- it's weird to GROUP BYs that increase the number of results

"it's weird to have ..."

+-- nonsensically that seems to be allowed
+UPDATE fewmore SET data = generate_series(4,9);

"nonsense that seems to be allowed..."

+-- SRFs are now allowed in RETURNING
+INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);

s/now/not/, apparently


More to come later, but 0001 is pushable with these fixes.
        regards, tom lane



Andres Freund <andres@anarazel.de> writes:
> 0002-Shore-up-some-weird-corner-cases-for-targetlist-SRFs.patch
>   Forbid UPDATE ... SET foo = SRF() and ORDER BY / GROUP BY containing
>   SRFs that would change the number of returned rows.  Without the
>   latter e.g. SELECT 1 ORDER BY generate_series(1,10); returns 10 rows.

I'm on board with disallowing SRFs in UPDATE, because it produces
underdetermined and unspecified results; but the other restriction
seems 100% arbitrary.  There is no semantic difference betweenSELECT a, b FROM ... ORDER BY srf();
andSELECT a, b, srf() FROM ... ORDER BY 3;
except that in the first case the ordering column doesn't get returned to
the client.  I do not see why that's so awful that we should make it fail
after twenty years of allowing it.  And I certainly don't see why there
would be an implementation reason why we couldn't support it anymore
if we can still do the second one.
        regards, tom lane



Andres Freund <andres@anarazel.de> writes:
> 0003-Avoid-materializing-SRFs-in-the-FROM-list.patch
>   To avoid performance regressions from moving SRFM_ValuePerCall SRFs to
>   ROWS FROM, nodeFunctionscan.c needs to support not materializing
>   output.

Personally I'd put this one later, as it's a performance optimization not
part of the core patch IMO --- or is there something in the later ones
that directly depends on it?  Anyway I'm setting it aside for now.

> 0004-Allow-ROWS-FROM-to-return-functions-as-single-record.patch
>   To allow transforming SELECT record_srf(); nodeFunctionscan.c needs to
>   learn to return the result as a record.  I chose
>   ROWS FROM (record_srf() AS ()) as the syntax for that. It doesn't
>   necessarily have to be SQL exposed, but it does make testing easier.

The proposed commit message is wrong, as it claims aclexplode()
demonstrates the problem which it doesn't --- we get the column set
from the function's declared OUT parameters.

I can't say that I like the proposed syntax much.  What about leaving out
any syntax changes, and simply saying that "if the function returns record
and hasn't got OUT parameters, then return its result as an unexpanded
record"?  That might not take much more than removing the error check ;-)

A possible objection is that then you could not get the no-expansion
behavior for functions that return named composite types or have OUT
parameters that effectively give them known composite types.  From a
semantic standpoint we could fix that by saying "just cast the result to
record", ie ROWS FROM (aclexplode('whatever')::record) would give the
no-expansion behavior.  I'm not sure if there might be any implementation
glitches in the way of doing it like that.  Also there seems to be some
syntactic issue with it ... actually, the current behavior there is just
weird:

regression=# select * from rows from (aclexplode('{=r/postgres}')::record); 
ERROR:  syntax error at or near "::"
LINE 1: ...lect * from rows from (aclexplode('{=r/postgres}')::record);
          ^
 
regression=# select * from rows from (cast(aclexplode('{=r/postgres}') as record));grantor | grantee | privilege_type |
is_grantable
 
---------+---------+----------------+--------------     10 |       0 | SELECT         | f
(1 row)

I was not aware that there was *anyplace* in the grammar where :: and CAST
behaved differently, and I'm not very pleased to find this one.

I haven't looked at the code, as there probably isn't much point in
reviewing in any detail till we choose the syntax.
        regards, tom lane



On 2016-09-12 10:19:14 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> 
> > 0001-Add-some-more-targetlist-srf-tests.patch
> >   Add some test.
> 
> I think you should go ahead and push this tests-adding patch now, as it
> adds documentation of the current behavior that is a good thing to have
> independently of what the rest of the patchset does.  Also, I'm worried
> that some of the GROUP BY tests might have machine-dependent results
> (if they are implemented by hashing) so it would be good to get in a few
> buildfarm cycles and let that settle out before we start changing the
> answers.

Generally a sound plan - I started to noticeably expand it though,
there's some important edge cases it didn't cover.


> Although tsrf.sql doesn't currently need to create any views, it doesn't
> seem like a great idea to assume that it never will.  Maybe add this
> after misc_functions in the previous parallel group, instead?

WFM


> +-- it's weird to GROUP BYs that increase the number of results
> 
> "it's weird to have ..."
> 
> +-- nonsensically that seems to be allowed
> +UPDATE fewmore SET data = generate_series(4,9);
> 
> "nonsense that seems to be allowed..."
> 
> +-- SRFs are now allowed in RETURNING
> +INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
> 
> s/now/not/, apparently

Err, yes. Will update.


Greetings,

Andres Freund



On 2016-09-12 11:29:37 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > 0002-Shore-up-some-weird-corner-cases-for-targetlist-SRFs.patch
> >   Forbid UPDATE ... SET foo = SRF() and ORDER BY / GROUP BY containing
> >   SRFs that would change the number of returned rows.  Without the
> >   latter e.g. SELECT 1 ORDER BY generate_series(1,10); returns 10 rows.
> 
> I'm on board with disallowing SRFs in UPDATE, because it produces
> underdetermined and unspecified results; but the other restriction
> seems 100% arbitrary.  There is no semantic difference between
>     SELECT a, b FROM ... ORDER BY srf();
> and
>     SELECT a, b, srf() FROM ... ORDER BY 3;
> except that in the first case the ordering column doesn't get returned to
> the client.  I do not see why that's so awful that we should make it fail
> after twenty years of allowing it.

I do think it's awful that an ORDER BY / GROUP BY changes the number of
rows processed. This should never have been allowed. You just need a
little typo somewhere that makes the targetlist entry not match the
ORDER/GROUP BY anymore and your results will differ in weird ways -
rather hard to debug in the GROUP BY case.


> And I certainly don't see why there
> would be an implementation reason why we couldn't support it anymore
> if we can still do the second one.

There's nothing requiring this here, indeed.


Andres



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 11:29:37 -0400, Tom Lane wrote:
>> I'm on board with disallowing SRFs in UPDATE, because it produces
>> underdetermined and unspecified results; but the other restriction
>> seems 100% arbitrary.  There is no semantic difference between
>> SELECT a, b FROM ... ORDER BY srf();
>> and
>> SELECT a, b, srf() FROM ... ORDER BY 3;
>> except that in the first case the ordering column doesn't get returned to
>> the client.  I do not see why that's so awful that we should make it fail
>> after twenty years of allowing it.

> I do think it's awful that an ORDER BY / GROUP BY changes the number of
> rows processed. This should never have been allowed.

Meh.  That's just an opinion, and it's a bit late to be making such
changes.  I think the general consensus of the previous discussion was
that we would preserve existing tSRF behavior as far as it was reasonably
practical to do so, with the exception that there's wide agreement that
the least-common-multiple rule for number of rows emitted is bad.  I do
not think you're going to get anywhere near that level of agreement that
a SRF appearing only in ORDER BY is bad.
        regards, tom lane



Hi,

On 2016-09-12 12:10:01 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > 0003-Avoid-materializing-SRFs-in-the-FROM-list.patch
> >   To avoid performance regressions from moving SRFM_ValuePerCall SRFs to
> >   ROWS FROM, nodeFunctionscan.c needs to support not materializing
> >   output.
> 
> Personally I'd put this one later, as it's a performance optimization not
> part of the core patch IMO --- or is there something in the later ones
> that directly depends on it?  Anyway I'm setting it aside for now.

Not strongly dependant. But the ROWS FROM stuff touches a lot of the
same code. And I wanted to implement this before ripping out the current
implementation, to allow for meaningful performance tests.

> > 0004-Allow-ROWS-FROM-to-return-functions-as-single-record.patch
> >   To allow transforming SELECT record_srf(); nodeFunctionscan.c needs to
> >   learn to return the result as a record.  I chose
> >   ROWS FROM (record_srf() AS ()) as the syntax for that. It doesn't
> >   necessarily have to be SQL exposed, but it does make testing easier.
> 
> The proposed commit message is wrong, as it claims aclexplode()
> demonstrates the problem which it doesn't --- we get the column set
> from the function's declared OUT parameters.

Oops. I'd probably tested with some self defined function and was
looking for an example...


> I can't say that I like the proposed syntax much.

Me neither. But I haven't really found a better approach.  It seems
kinda consistent to have ROWS FROM (... AS ()) change the picked out
columns to 0, and just return the whole thing.


> What about leaving out
> any syntax changes, and simply saying that "if the function returns record
> and hasn't got OUT parameters, then return its result as an unexpanded
> record"?  That might not take much more than removing the error check ;-)

Having the ability to do this for non-record returning functions turned
out to be quite convenient. Otherwise we need to create ROW()
expressions for composite returning functions, which is neither cheap,
nor fun..

As you say, that might be doable with some form of casting, but,
ugh. I'm actually kind of surprised that even works. The function call
that nodeFunctionscan.c sees, isn't a function call, much less a set
returning one. Which means this hits the direct_function_call == false
path in ExecMakeTableFunctionResult(). If it didn't, we'd have hit    /* We don't allow sets in the arguments of the
tablefunction */    if (argDone != ExprSingleResult)        ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),                errmsg("set-valued function called in context that cannot
accepta set")));
 
therein. Which you'd hit e.g. with
SELECT * FROM ROWS FROM (int4mul(generate_series(1, 2), 3));

Thus this actually relies on the SRF code path in execQual.c;
the thing we want to rip out...

> A possible objection is that then you could not get the no-expansion
> behavior for functions that return named composite types or have OUT
> parameters that effectively give them known composite types.  From a
> semantic standpoint we could fix that by saying "just cast the result to
> record", ie ROWS FROM (aclexplode('whatever')::record) would give the
> no-expansion behavior.  I'm not sure if there might be any implementation
> glitches in the way of doing it like that.

See above.  Personally I think just using explicit syntax is cleaner,
but I don't feel like arguing about this a whole lot.


- Andres



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 12:10:01 -0400, Tom Lane wrote:
>> I can't say that I like the proposed syntax much.

> Me neither. But I haven't really found a better approach.  It seems
> kinda consistent to have ROWS FROM (... AS ()) change the picked out
> columns to 0, and just return the whole thing.

I just remembered that we allow zero-column composite types, which
makes this proposal formally ambiguous.  So we really need a different
syntax.  I'm not especially in love with the cast-to-record idea, but
it does dodge that problem.

Stepping back a little bit ... way back at the start of this thread
you muttered about possibly implementing tSRFs as a special pipeline
node type, a la Result.  That would have the same benefits in terms
of being able to take SRF support out of the main execQual code paths.
I, and I think some other people, felt that the LATERAL approach would
be a cleaner answer --- but now that we're seeing some of the messy
details required to make the LATERAL way work, I'm beginning to have
second thoughts.  I wonder if we should do at least a POC implementation
of the other way to get a better fix on which way is really cleaner.

Also, one of the points that's come up repeatedly in these discussions
is the way that the parser's implementation of *-expansion sucks for
composite-returning functions.  That is, if you writeSELECT (foo(...)).* FROM ...
you getSELECT (foo(...)).col1, (foo(...)).col2, ... FROM ...
so that the function is executed N times not once.  We had discussed
fixing that for setof-composite-returning functions by folding multiple
identical SRF calls into a single LATERAL entry, but that doesn't
directly fix the problem for non-SRF composite functions.  Also the
whole idea of having the planner undo the parser's damage in this way
is kinda grotty, not least because we can't safely combine multiple
calls of volatile functions, so it only works for not-volatile ones.

That line of thought leads to the idea that if we could have the *parser*
do the transformation to LATERAL form, we could avoid breaking a
composite-returning function call into multiple copies in the first place.
I had said that I didn't think we wanted this transformation done in the
parser, but maybe this is a sufficient reason to do so.

If we think in terms of pipeline evaluation nodes rather than LATERAL,
we could implement the above by having the parser emit multiple levels
of SELECT some-expressions FROM (SELECT some-expressions FROM ...),
with SRFs being rigidly separated into their own evaluation levels.

I'm not certain that any of these ideas are worth the electrons they're
written on, but I do think we ought to consider alternatives and not
just push forward with committing a first-draft implementation.
        regards, tom lane



Hi,


On 2016-09-12 13:26:20 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 12:10:01 -0400, Tom Lane wrote:
> >> I can't say that I like the proposed syntax much.
> 
> > Me neither. But I haven't really found a better approach.  It seems
> > kinda consistent to have ROWS FROM (... AS ()) change the picked out
> > columns to 0, and just return the whole thing.
> 
> I just remembered that we allow zero-column composite types, which
> makes this proposal formally ambiguous.

Well, we errored out in the grammar for AS () so far... We might want to
fix that independently.


> Stepping back a little bit ... way back at the start of this thread
> you muttered about possibly implementing tSRFs as a special pipeline
> node type, a la Result.  That would have the same benefits in terms
> of being able to take SRF support out of the main execQual code paths.
> I, and I think some other people, felt that the LATERAL approach would
> be a cleaner answer --- but now that we're seeing some of the messy
> details required to make the LATERAL way work, I'm beginning to have
> second thoughts.  I wonder if we should do at least a POC implementation
> of the other way to get a better fix on which way is really cleaner.

I'm not particularly in love in restarting with a different approach. I
think fixing the ROWS FROM expansion is the only really painful bit, and
that seems like it's independently beneficial to allow for suppression
of expansion there.  I'm working on this to actually be finally able to
get some stuff from the "faster executor" thread in a committable
shape,...  The other stuff like making SELECT * FROM func; not
materialize also seems independently useful; it's something people have
complained about repeatedly over the years.


I actually had started to work on a Result style approach, and I don't
think it turned out that nice. But I didn't complete it, so I might just
be wrong.


> Also, one of the points that's come up repeatedly in these discussions
> is the way that the parser's implementation of *-expansion sucks for
> composite-returning functions.  That is, if you write
>     SELECT (foo(...)).* FROM ...
> you get
>     SELECT (foo(...)).col1, (foo(...)).col2, ... FROM ...
> so that the function is executed N times not once.  We had discussed
> fixing that for setof-composite-returning functions by folding multiple
> identical SRF calls into a single LATERAL entry, but that doesn't
> directly fix the problem for non-SRF composite functions.  Also the
> whole idea of having the planner undo the parser's damage in this way
> is kinda grotty, not least because we can't safely combine multiple
> calls of volatile functions, so it only works for not-volatile ones.


> That line of thought leads to the idea that if we could have the *parser*
> do the transformation to LATERAL form, we could avoid breaking a
> composite-returning function call into multiple copies in the first place.
> I had said that I didn't think we wanted this transformation done in the
> parser, but maybe this is a sufficient reason to do so.

I still don't like doing all this is in the parser. It'd just trigger
complaints of users that we're changing their query structure, and we'd
have to solve a good bit of the same problems we have to solve here.

If we really want to reduce the expansion cost - and to me that's a
largely independent issue from this - it seems better to have the parser
emit some structure that's easily recognized at plan time, rather than
have the praser do all the work.


Andres



On 2016-09-12 13:26:20 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 12:10:01 -0400, Tom Lane wrote:
> >> I can't say that I like the proposed syntax much.
> 
> > Me neither. But I haven't really found a better approach.  It seems
> > kinda consistent to have ROWS FROM (... AS ()) change the picked out
> > columns to 0, and just return the whole thing.
> 
> I just remembered that we allow zero-column composite types, which
> makes this proposal formally ambiguous.  So we really need a different
> syntax.  I'm not especially in love with the cast-to-record idea, but
> it does dodge that problem.

I kind of like ROWS FROM (... AS VALUE), that seems to confer the
meaning quite well. As VALUE isn't a reserved keyword, that'd afaik only
really work inside ROWS FROM() where AS is required.



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 13:26:20 -0400, Tom Lane wrote:
>> Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 12:10:01 -0400, Tom Lane wrote:
>>>> I can't say that I like the proposed syntax much.

>>> Me neither. But I haven't really found a better approach.  It seems
>>> kinda consistent to have ROWS FROM (... AS ()) change the picked out
>>> columns to 0, and just return the whole thing.

>> I just remembered that we allow zero-column composite types, which
>> makes this proposal formally ambiguous.  So we really need a different
>> syntax.  I'm not especially in love with the cast-to-record idea, but
>> it does dodge that problem.

> I kind of like ROWS FROM (... AS VALUE), that seems to confer the
> meaning quite well. As VALUE isn't a reserved keyword, that'd afaik only
> really work inside ROWS FROM() where AS is required.

Hm, wouldn't ... AS RECORD convey the meaning better?

(Although once you look at it that way, it's just a cast spelled in
an idiosyncratic fashion.)
        regards, tom lane



On 2016-09-12 13:48:05 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 13:26:20 -0400, Tom Lane wrote:
> >> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 12:10:01 -0400, Tom Lane wrote:
> >>>> I can't say that I like the proposed syntax much.
>
> >>> Me neither. But I haven't really found a better approach.  It seems
> >>> kinda consistent to have ROWS FROM (... AS ()) change the picked out
> >>> columns to 0, and just return the whole thing.
>
> >> I just remembered that we allow zero-column composite types, which
> >> makes this proposal formally ambiguous.  So we really need a different
> >> syntax.  I'm not especially in love with the cast-to-record idea, but
> >> it does dodge that problem.
>
> > I kind of like ROWS FROM (... AS VALUE), that seems to confer the
> > meaning quite well. As VALUE isn't a reserved keyword, that'd afaik only
> > really work inside ROWS FROM() where AS is required.
>
> Hm, wouldn't ... AS RECORD convey the meaning better?

I was kind of envisioning AS VALUE to work for composite types without
removing their original type (possibly even for TYPEFUNC_SCALAR
ones). That, for one, makes the SRF to ROWS FROM conversion easier, and
for another seems generally useful. composites keeping their type with
AS RECORD seems a bit confusing.  There's also the issue that VALUE is
already a keyword, record not...

> (Although once you look at it that way, it's just a cast spelled in
> an idiosyncratic fashion.)

Well, not quite, by virtue of keeping the original type around. After a
record cast you likely couldn't directly access the columns anymore,
even if it were a known composite type, right?

Andres



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 13:48:05 -0400, Tom Lane wrote:
>> Andres Freund <andres@anarazel.de> writes:
>>> I kind of like ROWS FROM (... AS VALUE), that seems to confer the
>>> meaning quite well. As VALUE isn't a reserved keyword, that'd afaik only
>>> really work inside ROWS FROM() where AS is required.

>> Hm, wouldn't ... AS RECORD convey the meaning better?

> I was kind of envisioning AS VALUE to work for composite types without
> removing their original type (possibly even for TYPEFUNC_SCALAR
> ones).

Maybe.  A problem with any of these proposals though is that there's no
place to put a column alias.  Yeah, you can stick it on outside the ROWS
FROM, but it seems a bit non-orthogonal to have to do it that way when
you can do it inside the ROWS FROM when adding a coldeflist.

Maybe we could do it like
ROWS FROM (func(...) AS alias)

where the difference from a coldeflist is that there's no parenthesized
list of names/types.  It's a bit weird that adding an alias makes for
a semantic not just naming difference, but it's no weirder than these
other ideas.

>> (Although once you look at it that way, it's just a cast spelled in
>> an idiosyncratic fashion.)

> Well, not quite, by virtue of keeping the original type around. After a
> record cast you likely couldn't directly access the columns anymore,
> even if it were a known composite type, right?

Same is true for any of these syntax proposals, no?  So far as the rest of
the query is concerned, the function output is going to be an anonymous
record type.
        regards, tom lane



On 2016-09-12 14:05:33 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 13:48:05 -0400, Tom Lane wrote:
> >> Andres Freund <andres@anarazel.de> writes:
> >>> I kind of like ROWS FROM (... AS VALUE), that seems to confer the
> >>> meaning quite well. As VALUE isn't a reserved keyword, that'd afaik only
> >>> really work inside ROWS FROM() where AS is required.
> 
> >> Hm, wouldn't ... AS RECORD convey the meaning better?
> 
> > I was kind of envisioning AS VALUE to work for composite types without
> > removing their original type (possibly even for TYPEFUNC_SCALAR
> > ones).
> 
> Maybe.  A problem with any of these proposals though is that there's no
> place to put a column alias.  Yeah, you can stick it on outside the ROWS
> FROM, but it seems a bit non-orthogonal to have to do it that way when
> you can do it inside the ROWS FROM when adding a coldeflist.

I don't necessarily see that as a problem. The coldeflists inside ROWS
FROM() already don't allow assigning aliases for !record/composite
types, and they require specifying types.


> >> (Although once you look at it that way, it's just a cast spelled in
> >> an idiosyncratic fashion.)
> 
> > Well, not quite, by virtue of keeping the original type around. After a
> > record cast you likely couldn't directly access the columns anymore,
> > even if it were a known composite type, right?
> 
> Same is true for any of these syntax proposals, no?  So far as the rest of
> the query is concerned, the function output is going to be an anonymous
> record type.

Not for composite types, no. As implemented ROWS FROM (.. AS()) does:
CREATE OR REPLACE FUNCTION get_pg_class() RETURNS SETOF pg_class LANGUAGE sql AS $$SELECT * FROM pg_class;$$;
SELECT DISTINCT pg_typeof(f) FROM ROWS FROM (get_pg_class() AS ()) f;
┌───────────┐
│ pg_typeof │
├───────────┤
│ pg_class  │
└───────────┘
(1 row)
SELECT (f).relname FROM ROWS FROM (get_pg_class() AS ()) f LIMIT 1;
┌────────────────┐
│    relname     │
├────────────────┤
│ pg_toast_77994 │
└────────────────┘
(1 row)
which seems sensible to me.



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 13:26:20 -0400, Tom Lane wrote:
>> Stepping back a little bit ... way back at the start of this thread
>> you muttered about possibly implementing tSRFs as a special pipeline
>> node type, a la Result.  That would have the same benefits in terms
>> of being able to take SRF support out of the main execQual code paths.
>> I, and I think some other people, felt that the LATERAL approach would
>> be a cleaner answer --- but now that we're seeing some of the messy
>> details required to make the LATERAL way work, I'm beginning to have
>> second thoughts.  I wonder if we should do at least a POC implementation
>> of the other way to get a better fix on which way is really cleaner.

> I'm not particularly in love in restarting with a different approach. I
> think fixing the ROWS FROM expansion is the only really painful bit, and
> that seems like it's independently beneficial to allow for suppression
> of expansion there.

Um, I dunno.  You've added half a thousand lines of not-highly-readable-
nor-extensively-commented code to the planner; that certainly reaches *my*
threshold of pain.  I'm also growing rather concerned that the LATERAL
approach is going to lock us into some unremovable incompatibilities
no matter how much we might regret that later (and in view of how quickly
I got my wrist slapped in <001201d18524$f84c4580$e8e4d080$@pcorp.us>,
I am afraid there may be more pushback awaiting us than we think).
If we go with a Result-like tSRF evaluation node, then whether we change
semantics or not becomes mostly a matter of what that node does.  It could
become basically a wrapper around the existing ExecTargetList() logic if
we needed to provide backwards-compatible behavior.

> I actually had started to work on a Result style approach, and I don't
> think it turned out that nice. But I didn't complete it, so I might just
> be wrong.

It's also possible that it's easier now in the post-pathification code
base than it was before.  After contemplating my navel for awhile, it
seems like the core of the planner code for a Result-like approach would
be something very close to make_group_input_target(), plus a thing like
pull_var_clause() that extracts SRFs rather than Vars.  Those two
functions, including their lengthy comments, weigh in at ~250 lines.
Admittedly there'd be some boilerplate on top of that, if we invent a
new plan node type rather than extending Result, but not all that much.

If you like, I'll have a go at drafting a patch in that style.  Do you
happen to still have the executor side of what you did, so I don't have
to reinvent that?
        regards, tom lane



On 2016-09-12 17:36:07 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 13:26:20 -0400, Tom Lane wrote:
> >> Stepping back a little bit ... way back at the start of this thread
> >> you muttered about possibly implementing tSRFs as a special pipeline
> >> node type, a la Result.  That would have the same benefits in terms
> >> of being able to take SRF support out of the main execQual code paths.
> >> I, and I think some other people, felt that the LATERAL approach would
> >> be a cleaner answer --- but now that we're seeing some of the messy
> >> details required to make the LATERAL way work, I'm beginning to have
> >> second thoughts.  I wonder if we should do at least a POC implementation
> >> of the other way to get a better fix on which way is really cleaner.
> 
> > I'm not particularly in love in restarting with a different approach. I
> > think fixing the ROWS FROM expansion is the only really painful bit, and
> > that seems like it's independently beneficial to allow for suppression
> > of expansion there.
> 
> Um, I dunno.  You've added half a thousand lines of not-highly-readable-
> nor-extensively-commented code to the planner; that certainly reaches *my*
> threshold of pain.

Well, I certainly plan (and started to) make that code easier to
understand, and better commented.  It also removes ~1400 LOC of not easy
to understand code... A good chunk of that'd would also be removed with
a Result style approach, but far from all.


> I'm also growing rather concerned that the LATERAL
> approach is going to lock us into some unremovable incompatibilities
> no matter how much we might regret that later (and in view of how quickly
> I got my wrist slapped in <001201d18524$f84c4580$e8e4d080$@pcorp.us>,
> I am afraid there may be more pushback awaiting us than we think).

I don't think it'd be all that hard to add something like the current
LCM behaviour into nodeFunctionscan.c if we really wanted. But I think
it'll be better to just say no here.


> If we go with a Result-like tSRF evaluation node, then whether we change
> semantics or not becomes mostly a matter of what that node does.  It could
> become basically a wrapper around the existing ExecTargetList() logic if
> we needed to provide backwards-compatible behavior.

As you previously objected: If we keep ExecTargetList() style logic, we
need to keep most of execQual.c's handling of ExprMultipleResult et al,
and that's going to prevent the stuff I want to work on. Because
handling ExprMultipleResult in all these places is a serious issue
WRT making expression evaluation faster.  If we find a good answer to
that, I'd be more open to pursuing this approach.


> > I actually had started to work on a Result style approach, and I don't
> > think it turned out that nice. But I didn't complete it, so I might just
> > be wrong.
> 
> It's also possible that it's easier now in the post-pathification code
> base than it was before.  After contemplating my navel for awhile, it
> seems like the core of the planner code for a Result-like approach would
> be something very close to make_group_input_target(), plus a thing like
> pull_var_clause() that extracts SRFs rather than Vars.  Those two
> functions, including their lengthy comments, weigh in at ~250 lines.
> Admittedly there'd be some boilerplate on top of that, if we invent a
> new plan node type rather than extending Result, but not all that much.

That's pretty much what I did (or rather started to do), yes.  I had
something that was called from grouping_planner() that added the new
node ontop of the original set of paths, after grouping or after
distinct, depending on where SRFs were referenced.  The biggest benefit
I saw with that is that there's no need to push things into a subquery,
and that the ordering is a lot more explicit.


> If you like, I'll have a go at drafting a patch in that style.  Do you
> happen to still have the executor side of what you did, so I don't have
> to reinvent that?

The executor side is actually what I found harder here. Either we end up
keeping most of ExecQual's handling, or we reinvent a good deal of
separate logic.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 17:36:07 -0400, Tom Lane wrote:
>> Um, I dunno.  You've added half a thousand lines of not-highly-readable-
>> nor-extensively-commented code to the planner; that certainly reaches *my*
>> threshold of pain.

> Well, I certainly plan (and started to) make that code easier to
> understand, and better commented.  It also removes ~1400 LOC of not easy
> to understand code... A good chunk of that'd would also be removed with
> a Result style approach, but far from all.

Hm, I've not studied 0006 yet, but surely that's executor code that would
go away with *any* approach that takes away the need for generic execQual
to support SRFs?  I don't see that it counts while discussing which way
we take to reach that point.

>> I'm also growing rather concerned that the LATERAL
>> approach is going to lock us into some unremovable incompatibilities
>> no matter how much we might regret that later (and in view of how quickly
>> I got my wrist slapped in <001201d18524$f84c4580$e8e4d080$@pcorp.us>,
>> I am afraid there may be more pushback awaiting us than we think).

> I don't think it'd be all that hard to add something like the current
> LCM behaviour into nodeFunctionscan.c if we really wanted. But I think
> it'll be better to just say no here.

"Just say no" soon translates to memes about "disasters like the removal
of implicit casting" (which in fact is not what 8.3 did, but I've grown
pretty damn tired of the amount of bitching that that cleanup did and
still does provoke).  In any case, it feels like the LATERAL approach is
locking us into more and subtler incompatibilities than just that one.

>> If we go with a Result-like tSRF evaluation node, then whether we change
>> semantics or not becomes mostly a matter of what that node does.  It could
>> become basically a wrapper around the existing ExecTargetList() logic if
>> we needed to provide backwards-compatible behavior.

> As you previously objected: If we keep ExecTargetList() style logic, we
> need to keep most of execQual.c's handling of ExprMultipleResult et al,
> and that's going to prevent the stuff I want to work on.

You're inventing objections.  It won't require that any more than the
LATERAL approach does; it's basically the same code as whatever
nodeFunctionscan is going to do, but packaged as a pipeline eval node
rather than a base scan node.  Or to be clearer: what I'm suggesting it
would contain is ExecTargetList's logic about restarting individual SRFs.
That wouldn't propagate into execQual because we would only allow SRFs at
the top level of the node's tlist, just like nodeFunctionscan does.
ExecMakeTableFunctionResult doesn't require the generic execQual code
to support SRFs today, and it still wouldn't.

In larger terms: the whole point here is to fish SRF calls up to the top
level of the tlist of whatever node is executing them, where they can be
special-cased by that node.  Their SRF-free argument expressions would be
evaluated by generic execQual.  AFAICS this goes through in the same way
from the executor's viewpoint whether we use LATERAL as the query
restructuring method or a SRF-capable variant of Result.  But it's now
looking to me like the latter would be a lot simpler from the point of
view of planner complexity, and in particular from the point of view of
proving correctness (equivalence of the query transformation).
        regards, tom lane



Hi,

On 2016-09-12 18:35:03 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > I don't think it'd be all that hard to add something like the current
> > LCM behaviour into nodeFunctionscan.c if we really wanted. But I think
> > it'll be better to just say no here.
> 
> "Just say no" soon translates to memes about "disasters like the removal
> of implicit casting" (which in fact is not what 8.3 did, but I've grown
> pretty damn tired of the amount of bitching that that cleanup did and
> still does provoke).  In any case, it feels like the LATERAL approach is
> locking us into more and subtler incompatibilities than just that one.

I can't see those being equivalent impact-wise. Note that the person
bitching most loudly about the "implicit casting" thing (Merlin Moncure)
is voting to remove the LCM behaviour ;)

I think we'll continue to get more bitching about the confusing LCM
behaviour than complaints the backward compat break would generate.


> >> If we go with a Result-like tSRF evaluation node, then whether we change
> >> semantics or not becomes mostly a matter of what that node does.  It could
> >> become basically a wrapper around the existing ExecTargetList() logic if
> >> we needed to provide backwards-compatible behavior.
> 
> > As you previously objected: If we keep ExecTargetList() style logic, we
> > need to keep most of execQual.c's handling of ExprMultipleResult et al,
> > and that's going to prevent the stuff I want to work on.
> 
> You're inventing objections.

Heh, it's actually your own objection ;)

http://archives.postgresql.org/message-id/32673.1464023429%40sss.pgh.pa.us


> It won't require that any more than the
> LATERAL approach does; it's basically the same code as whatever
> nodeFunctionscan is going to do, but packaged as a pipeline eval node
> rather than a base scan node.  Or to be clearer: what I'm suggesting it
> would contain is ExecTargetList's logic about restarting individual SRFs.
> That wouldn't propagate into execQual because we would only allow SRFs at
> the top level of the node's tlist, just like nodeFunctionscan does.
> ExecMakeTableFunctionResult doesn't require the generic execQual code
> to support SRFs today, and it still wouldn't.

(it accidentally does (see your cast example), but that that's besides
your point)

That might work.  It gets somewhat nasty though because you also need to
handle, SRF arguments to SRFs. And those again can contain nearly
arbitrary expressions inbetween. With the ROWS FROM approach that can be
fairly easily handled via LATERAL.  I guess what we could do here is to
use one pipeline node to evaluate all the argument SRFs, and then
another for the outer expression. Where the outer node would evaluate
the SRF arguments using normal expression evaluation, with the inner SRF
output replaced by a var.

I wonder how much duplication we'd end up between nodeFunctionscan.c and
nodeSRF (or whatever). We'd need the latter node to support ValuePerCall
in an non-materializing fashion as well.  Could we combine them somehow?


> In larger terms: the whole point here is to fish SRF calls up to the
> top level of the tlist of whatever node is executing them, where they
> can be special-cased by that node.  Their SRF-free argument
> expressions would be evaluated by generic execQual.  AFAICS this goes
> through in the same way from the executor's viewpoint whether we use
> LATERAL as the query restructuring method or a SRF-capable variant of
> Result.  But it's now looking to me like the latter would be a lot
> simpler from the point of view of planner complexity, and in
> particular from the point of view of proving correctness (equivalence
> of the query transformation).

It's nicer not to introduce subqueries from a two angles from my pov:
1) EXPLAINs will look more like the original query
2) The evaluation order of the non-srf part of the query, and the query  itself, will be easier. That's by the thing
I'mleast happy with the  LATERAL approach.
 

I don't think the code for adding these intermediate SRF evaluating
nodes will be noticeably simpler than what's in my prototype. We'll
still have to do the whole conversion recursively, we'll still need
complexity of figuring out whether to put those SRFs evaluations
before/after group by, order by, distinct on and window functions.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 18:35:03 -0400, Tom Lane wrote:
>> You're inventing objections.

> Heh, it's actually your own objection ;)
> http://archives.postgresql.org/message-id/32673.1464023429%40sss.pgh.pa.us

I'm changing my opinion in the light of unfavorable evidence.  Is that
wrong?

>> It won't require that any more than the
>> LATERAL approach does; it's basically the same code as whatever
>> nodeFunctionscan is going to do, but packaged as a pipeline eval node
>> rather than a base scan node.

> That might work.  It gets somewhat nasty though because you also need to
> handle, SRF arguments to SRFs. And those again can contain nearly
> arbitrary expressions inbetween. With the ROWS FROM approach that can be
> fairly easily handled via LATERAL.  I guess what we could do here is to
> use one pipeline node to evaluate all the argument SRFs, and then
> another for the outer expression. Where the outer node would evaluate
> the SRF arguments using normal expression evaluation, with the inner SRF
> output replaced by a var.

Right.  Nested SRFs translate to multiple ROWS-FROM RTEs with lateral
references in the one approach, and nested Result-thingies in the other.
It's pretty much the same thing mutatis mutandis, but I think it will
likely be a lot easier to get there from here with the Result-based
approach --- for example, we don't have to worry about forcing lateral
join order, and the ordering constraints vis-a-vis GROUP BY etc won't take
any great effort either.  Anyway I think it is worth trying.

> I wonder how much duplication we'd end up between nodeFunctionscan.c and
> nodeSRF (or whatever). We'd need the latter node to support ValuePerCall
> in an non-materializing fashion as well.  Could we combine them somehow?

Yeah, I was wondering that too.  I doubt that we want to make one node
type do both things --- the fact that Result comes in two flavors with
different semantics (with or without an input node) isn't very nice IMO,
and this would be almost that identical case.  But maybe they could share
some code at the level of ExecMakeTableFunctionResult.  (I've not looked
at your executor changes yet, not sure how much of that still exists.)

> I don't think the code for adding these intermediate SRF evaluating
> nodes will be noticeably simpler than what's in my prototype. We'll
> still have to do the whole conversion recursively, we'll still need
> complexity of figuring out whether to put those SRFs evaluations
> before/after group by, order by, distinct on and window functions.

I think it will slot into the code that's already there rather more
easily than what you've done, because we already *have* code that makes
decisions in that form.  We just need to teach it to break down what
it now thinks of as a single projection step into N+1 steps when there
are N levels of nested SRF present.  Anyway I'll draft a prototype and
then we can compare.
        regards, tom lane



On 2016-09-12 19:35:22 -0400, Tom Lane wrote:
> >> You're inventing objections.
> 
> > Heh, it's actually your own objection ;)
> > http://archives.postgresql.org/message-id/32673.1464023429%40sss.pgh.pa.us
> 
> I'm changing my opinion in the light of unfavorable evidence.  Is that
> wrong?

Nah, not at all.  I was just referring to "inventing".


> > I wonder how much duplication we'd end up between nodeFunctionscan.c and
> > nodeSRF (or whatever). We'd need the latter node to support ValuePerCall
> > in an non-materializing fashion as well.  Could we combine them somehow?
> 
> Yeah, I was wondering that too.  I doubt that we want to make one node
> type do both things --- the fact that Result comes in two flavors with
> different semantics (with or without an input node) isn't very nice IMO,
> and this would be almost that identical case.

It might not, agreed. That imo has to be taken into acount calculating
the code costs - although the executor stuff usually is pretty boring in
comparison.


> But maybe they could share
> some code at the level of ExecMakeTableFunctionResult.  (I've not looked
> at your executor changes yet, not sure how much of that still exists.)

I'd split ExecMakeTableFunctionResult up, to allow for a percall mode,
and that seems like it'd still be needed to avoid performance
regressions.

It'd be an awfully large amount of code those two nodes would
duplicate. Excepting different rescan logic and ORDINALITY support,
nearly all the nodeFunctionscan.c code would be needed in both nodes.

> Anyway I'll draft a prototype and then we can compare.

Ok, cool.


Andres



On 2016-09-12 09:14:47 -0700, Andres Freund wrote:
> On 2016-09-12 10:19:14 -0400, Tom Lane wrote:
> > Andres Freund <andres@anarazel.de> writes:
> > 
> > > 0001-Add-some-more-targetlist-srf-tests.patch
> > >   Add some test.
> > 
> > I think you should go ahead and push this tests-adding patch now, as it
> > adds documentation of the current behavior that is a good thing to have
> > independently of what the rest of the patchset does.  Also, I'm worried
> > that some of the GROUP BY tests might have machine-dependent results
> > (if they are implemented by hashing) so it would be good to get in a few
> > buildfarm cycles and let that settle out before we start changing the
> > answers.
> 
> Generally a sound plan - I started to noticeably expand it though,
> there's some important edge cases it didn't cover.

Attached is a noticeably expanded set of tests, with fixes for the stuff
you'd found.  I plan to push that soon-ish.  Independent of the approach
we'll be choosing, increased coverage seems quite useful.

Andres



On 2016-09-12 16:56:32 -0700, Andres Freund wrote:
> On 2016-09-12 09:14:47 -0700, Andres Freund wrote:
> > On 2016-09-12 10:19:14 -0400, Tom Lane wrote:
> > > Andres Freund <andres@anarazel.de> writes:
> > >
> > > > 0001-Add-some-more-targetlist-srf-tests.patch
> > > >   Add some test.
> > >
> > > I think you should go ahead and push this tests-adding patch now, as it
> > > adds documentation of the current behavior that is a good thing to have
> > > independently of what the rest of the patchset does.  Also, I'm worried
> > > that some of the GROUP BY tests might have machine-dependent results
> > > (if they are implemented by hashing) so it would be good to get in a few
> > > buildfarm cycles and let that settle out before we start changing the
> > > answers.
> >
> > Generally a sound plan - I started to noticeably expand it though,
> > there's some important edge cases it didn't cover.
>
> Attached is a noticeably expanded set of tests, with fixes for the stuff
> you'd found.  I plan to push that soon-ish.  Independent of the approach
> we'll be choosing, increased coverage seems quite useful.

And for real.

Attachment
Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 16:56:32 -0700, Andres Freund wrote:
>> Attached is a noticeably expanded set of tests, with fixes for the stuff
>> you'd found.  I plan to push that soon-ish.  Independent of the approach
>> we'll be choosing, increased coverage seems quite useful.

> And for real.

Looks good to me, please push.
        regards, tom lane



On 2016-09-12 20:15:46 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 16:56:32 -0700, Andres Freund wrote:
> >> Attached is a noticeably expanded set of tests, with fixes for the stuff
> >> you'd found.  I plan to push that soon-ish.  Independent of the approach
> >> we'll be choosing, increased coverage seems quite useful.
> 
> > And for real.
> 
> Looks good to me, please push.

Done.



Andres Freund <andres@anarazel.de> writes:
> Attached is a significantly updated patch series (see the mail one up
> for details about what this is, I don't want to quote it in its
> entirety).

I've reviewed the portions of 0005 that have to do with making the parser
mark queries with hasTargetSRF.  The code as you had it was wrong because
it would set the flag as a consequence of SRFs in function RTEs, which
we don't want.  It seemed to me that the best way to fix that was to rely
on the parser's p_expr_kind mechanism to tell which part of the query
we're in, whereupon we might as well make the parser act more like it does
for aggregates and window functions and give a suitable error at parse
time for misplaced SRFs.  The attached isn't perfect, in that it doesn't
know about nesting restrictions (ie that SRFs must be at top level of a
function RTE), but we could improve that later if we wanted, and anyway
it's definitely a good bit nicer than before.

This also incorporates the part of 0002 that I agree with, namely
disallowing SRFs in UPDATE, since check_srf_call_placement() naturally
would do that.

I also renamed the flag to hasTargetSRFs, which is more parallel to
hasAggs and hasWindowFuncs, and made some effort to use it in place
of expression_returns_set() searches.

I'd like to go ahead and push this, since it's a necessary prerequisite
for either approach we might adopt for the rest of the patch series,
and the improved error reporting and avoidance of expensive
expression_returns_set searches make it a win IMO even if we were not
planning to do anything more with SRFs.

            regards, tom lane

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..dbd6094 100644
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** cookDefault(ParseState *pstate,
*** 2560,2573 ****

      /*
       * transformExpr() should have already rejected subqueries, aggregates,
!      * and window functions, based on the EXPR_KIND_ for a default expression.
!      *
!      * It can't return a set either.
       */
-     if (expression_returns_set(expr))
-         ereport(ERROR,
-                 (errcode(ERRCODE_DATATYPE_MISMATCH),
-                  errmsg("default expression must not return a set")));

      /*
       * Coerce the expression to the correct type and typmod, if given. This
--- 2560,2568 ----

      /*
       * transformExpr() should have already rejected subqueries, aggregates,
!      * window functions, and SRFs, based on the EXPR_KIND_ for a default
!      * expression.
       */

      /*
       * Coerce the expression to the correct type and typmod, if given. This
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4f39dad..71714bc 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyQuery(const Query *from)
*** 2731,2736 ****
--- 2731,2737 ----
      COPY_SCALAR_FIELD(resultRelation);
      COPY_SCALAR_FIELD(hasAggs);
      COPY_SCALAR_FIELD(hasWindowFuncs);
+     COPY_SCALAR_FIELD(hasTargetSRFs);
      COPY_SCALAR_FIELD(hasSubLinks);
      COPY_SCALAR_FIELD(hasDistinctOn);
      COPY_SCALAR_FIELD(hasRecursive);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4800165..29a090f 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalQuery(const Query *a, const Query
*** 921,926 ****
--- 921,927 ----
      COMPARE_SCALAR_FIELD(resultRelation);
      COMPARE_SCALAR_FIELD(hasAggs);
      COMPARE_SCALAR_FIELD(hasWindowFuncs);
+     COMPARE_SCALAR_FIELD(hasTargetSRFs);
      COMPARE_SCALAR_FIELD(hasSubLinks);
      COMPARE_SCALAR_FIELD(hasDistinctOn);
      COMPARE_SCALAR_FIELD(hasRecursive);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 90fecb1..7e092d7 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outQuery(StringInfo str, const Query *n
*** 2683,2688 ****
--- 2683,2689 ----
      WRITE_INT_FIELD(resultRelation);
      WRITE_BOOL_FIELD(hasAggs);
      WRITE_BOOL_FIELD(hasWindowFuncs);
+     WRITE_BOOL_FIELD(hasTargetSRFs);
      WRITE_BOOL_FIELD(hasSubLinks);
      WRITE_BOOL_FIELD(hasDistinctOn);
      WRITE_BOOL_FIELD(hasRecursive);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 894a48f..917e6c8 100644
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 238,243 ****
--- 238,244 ----
      READ_INT_FIELD(resultRelation);
      READ_BOOL_FIELD(hasAggs);
      READ_BOOL_FIELD(hasWindowFuncs);
+     READ_BOOL_FIELD(hasTargetSRFs);
      READ_BOOL_FIELD(hasSubLinks);
      READ_BOOL_FIELD(hasDistinctOn);
      READ_BOOL_FIELD(hasRecursive);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 04264b4..99b6bc8 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** check_output_expressions(Query *subquery
*** 2422,2428 ****
              continue;

          /* Functions returning sets are unsafe (point 1) */
!         if (expression_returns_set((Node *) tle->expr))
          {
              safetyInfo->unsafeColumns[tle->resno] = true;
              continue;
--- 2422,2429 ----
              continue;

          /* Functions returning sets are unsafe (point 1) */
!         if (subquery->hasTargetSRFs &&
!             expression_returns_set((Node *) tle->expr))
          {
              safetyInfo->unsafeColumns[tle->resno] = true;
              continue;
*************** remove_unused_subquery_outputs(Query *su
*** 2835,2841 ****
           * If it contains a set-returning function, we can't remove it since
           * that could change the number of rows returned by the subquery.
           */
!         if (expression_returns_set(texpr))
              continue;

          /*
--- 2836,2843 ----
           * If it contains a set-returning function, we can't remove it since
           * that could change the number of rows returned by the subquery.
           */
!         if (subquery->hasTargetSRFs &&
!             expression_returns_set(texpr))
              continue;

          /*
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index e28a8dc..74e4245 100644
*** a/src/backend/optimizer/plan/analyzejoins.c
--- b/src/backend/optimizer/plan/analyzejoins.c
*************** rel_is_distinct_for(PlannerInfo *root, R
*** 650,655 ****
--- 650,660 ----
  bool
  query_supports_distinctness(Query *query)
  {
+     /* we don't cope with SRFs, see comment below */
+     if (query->hasTargetSRFs)
+         return false;
+
+     /* check for features we can prove distinctness with */
      if (query->distinctClause != NIL ||
          query->groupClause != NIL ||
          query->groupingSets != NIL ||
*************** query_is_distinct_for(Query *query, List
*** 695,701 ****
       * specified columns, since those must be evaluated before de-duplication;
       * but it doesn't presently seem worth the complication to check that.)
       */
!     if (expression_returns_set((Node *) query->targetList))
          return false;

      /*
--- 700,706 ----
       * specified columns, since those must be evaluated before de-duplication;
       * but it doesn't presently seem worth the complication to check that.)
       */
!     if (query->hasTargetSRFs)
          return false;

      /*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 174210b..f657ffc 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 604,609 ****
--- 604,613 ----
          preprocess_expression(root, (Node *) parse->targetList,
                                EXPRKIND_TARGET);

+     /* Constant-folding might have removed all set-returning functions */
+     if (parse->hasTargetSRFs)
+         parse->hasTargetSRFs = expression_returns_set((Node *) parse->targetList);
+
      newWithCheckOptions = NIL;
      foreach(l, parse->withCheckOptions)
      {
*************** grouping_planner(PlannerInfo *root, bool
*** 1702,1717 ****
           * Figure out whether there's a hard limit on the number of rows that
           * query_planner's result subplan needs to return.  Even if we know a
           * hard limit overall, it doesn't apply if the query has any
!          * grouping/aggregation operations.  (XXX it also doesn't apply if the
!          * tlist contains any SRFs; but checking for that here seems more
!          * costly than it's worth, since root->limit_tuples is only used for
!          * cost estimates, and only in a small number of cases.)
           */
          if (parse->groupClause ||
              parse->groupingSets ||
              parse->distinctClause ||
              parse->hasAggs ||
              parse->hasWindowFuncs ||
              root->hasHavingQual)
              root->limit_tuples = -1.0;
          else
--- 1706,1719 ----
           * Figure out whether there's a hard limit on the number of rows that
           * query_planner's result subplan needs to return.  Even if we know a
           * hard limit overall, it doesn't apply if the query has any
!          * grouping/aggregation operations, or SRFs in the tlist.
           */
          if (parse->groupClause ||
              parse->groupingSets ||
              parse->distinctClause ||
              parse->hasAggs ||
              parse->hasWindowFuncs ||
+             parse->hasTargetSRFs ||
              root->hasHavingQual)
              root->limit_tuples = -1.0;
          else
*************** grouping_planner(PlannerInfo *root, bool
*** 1928,1934 ****
       * weird usage that it doesn't seem worth greatly complicating matters to
       * account for it.
       */
!     tlist_rows = tlist_returns_set_rows(tlist);
      if (tlist_rows > 1)
      {
          foreach(lc, current_rel->pathlist)
--- 1930,1940 ----
       * weird usage that it doesn't seem worth greatly complicating matters to
       * account for it.
       */
!     if (parse->hasTargetSRFs)
!         tlist_rows = tlist_returns_set_rows(tlist);
!     else
!         tlist_rows = 1;
!
      if (tlist_rows > 1)
      {
          foreach(lc, current_rel->pathlist)
*************** make_sort_input_target(PlannerInfo *root
*** 4995,5001 ****
               * Check for SRF or volatile functions.  Check the SRF case first
               * because we must know whether we have any postponed SRFs.
               */
!             if (expression_returns_set((Node *) expr))
              {
                  /* We'll decide below whether these are postponable */
                  col_is_srf[i] = true;
--- 5001,5008 ----
               * Check for SRF or volatile functions.  Check the SRF case first
               * because we must know whether we have any postponed SRFs.
               */
!             if (parse->hasTargetSRFs &&
!                 expression_returns_set((Node *) expr))
              {
                  /* We'll decide below whether these are postponable */
                  col_is_srf[i] = true;
*************** make_sort_input_target(PlannerInfo *root
*** 5034,5039 ****
--- 5041,5047 ----
          {
              /* For sortgroupref cols, just check if any contain SRFs */
              if (!have_srf_sortcols &&
+                 parse->hasTargetSRFs &&
                  expression_returns_set((Node *) expr))
                  have_srf_sortcols = true;
          }
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6edefb1..b5d3e94 100644
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
*************** simplify_EXISTS_query(PlannerInfo *root,
*** 1562,1568 ****
  {
      /*
       * We don't try to simplify at all if the query uses set operations,
!      * aggregates, grouping sets, modifying CTEs, HAVING, OFFSET, or FOR
       * UPDATE/SHARE; none of these seem likely in normal usage and their
       * possible effects are complex.  (Note: we could ignore an "OFFSET 0"
       * clause, but that traditionally is used as an optimization fence, so we
--- 1562,1568 ----
  {
      /*
       * We don't try to simplify at all if the query uses set operations,
!      * aggregates, SRFs, grouping sets, modifying CTEs, HAVING, OFFSET, or FOR
       * UPDATE/SHARE; none of these seem likely in normal usage and their
       * possible effects are complex.  (Note: we could ignore an "OFFSET 0"
       * clause, but that traditionally is used as an optimization fence, so we
*************** simplify_EXISTS_query(PlannerInfo *root,
*** 1573,1578 ****
--- 1573,1579 ----
          query->hasAggs ||
          query->groupingSets ||
          query->hasWindowFuncs ||
+         query->hasTargetSRFs ||
          query->hasModifyingCTE ||
          query->havingQual ||
          query->limitOffset ||
*************** simplify_EXISTS_query(PlannerInfo *root,
*** 1614,1626 ****
      }

      /*
-      * Mustn't throw away the targetlist if it contains set-returning
-      * functions; those could affect whether zero rows are returned!
-      */
-     if (expression_returns_set((Node *) query->targetList))
-         return false;
-
-     /*
       * Otherwise, we can throw away the targetlist, as well as any GROUP,
       * WINDOW, DISTINCT, and ORDER BY clauses; none of those clauses will
       * change a nonzero-rows result to zero rows or vice versa.  (Furthermore,
--- 1615,1620 ----
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index a334f15..878db9b 100644
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** pull_up_simple_subquery(PlannerInfo *roo
*** 1188,1195 ****
      parse->hasSubLinks |= subquery->hasSubLinks;

      /*
!      * subquery won't be pulled up if it hasAggs or hasWindowFuncs, so no work
!      * needed on those flags
       */

      /*
--- 1188,1195 ----
      parse->hasSubLinks |= subquery->hasSubLinks;

      /*
!      * subquery won't be pulled up if it hasAggs, hasWindowFuncs, or
!      * hasTargetSRFs, so no work needed on those flags
       */

      /*
*************** is_simple_subquery(Query *subquery, Rang
*** 1419,1426 ****
          return false;

      /*
!      * Can't pull up a subquery involving grouping, aggregation, sorting,
!      * limiting, or WITH.  (XXX WITH could possibly be allowed later)
       *
       * We also don't pull up a subquery that has explicit FOR UPDATE/SHARE
       * clauses, because pullup would cause the locking to occur semantically
--- 1419,1426 ----
          return false;

      /*
!      * Can't pull up a subquery involving grouping, aggregation, SRFs,
!      * sorting, limiting, or WITH.  (XXX WITH could possibly be allowed later)
       *
       * We also don't pull up a subquery that has explicit FOR UPDATE/SHARE
       * clauses, because pullup would cause the locking to occur semantically
*************** is_simple_subquery(Query *subquery, Rang
*** 1430,1435 ****
--- 1430,1436 ----
       */
      if (subquery->hasAggs ||
          subquery->hasWindowFuncs ||
+         subquery->hasTargetSRFs ||
          subquery->groupClause ||
          subquery->groupingSets ||
          subquery->havingQual ||
*************** is_simple_subquery(Query *subquery, Rang
*** 1543,1557 ****
      }

      /*
-      * Don't pull up a subquery that has any set-returning functions in its
-      * targetlist.  Otherwise we might well wind up inserting set-returning
-      * functions into places where they mustn't go, such as quals of higher
-      * queries.  This also ensures deletion of an empty jointree is valid.
-      */
-     if (expression_returns_set((Node *) subquery->targetList))
-         return false;
-
-     /*
       * Don't pull up a subquery that has any volatile functions in its
       * targetlist.  Otherwise we might introduce multiple evaluations of these
       * functions, if they get copied to multiple places in the upper query,
--- 1544,1549 ----
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e1baf71..663ffe0 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** inline_function(Oid funcid, Oid result_t
*** 4449,4454 ****
--- 4449,4455 ----
          querytree->utilityStmt ||
          querytree->hasAggs ||
          querytree->hasWindowFuncs ||
+         querytree->hasTargetSRFs ||
          querytree->hasSubLinks ||
          querytree->cteList ||
          querytree->rtable ||
*************** inline_function(Oid funcid, Oid result_t
*** 4489,4505 ****
      Assert(!modifyTargetList);

      /*
!      * Additional validity checks on the expression.  It mustn't return a set,
!      * and it mustn't be more volatile than the surrounding function (this is
!      * to avoid breaking hacks that involve pretending a function is immutable
!      * when it really ain't).  If the surrounding function is declared strict,
!      * then the expression must contain only strict constructs and must use
!      * all of the function parameters (this is overkill, but an exact analysis
!      * is hard).
       */
-     if (expression_returns_set(newexpr))
-         goto fail;
-
      if (funcform->provolatile == PROVOLATILE_IMMUTABLE &&
          contain_mutable_functions(newexpr))
          goto fail;
--- 4490,4502 ----
      Assert(!modifyTargetList);

      /*
!      * Additional validity checks on the expression.  It mustn't be more
!      * volatile than the surrounding function (this is to avoid breaking hacks
!      * that involve pretending a function is immutable when it really ain't).
!      * If the surrounding function is declared strict, then the expression
!      * must contain only strict constructs and must use all of the function
!      * parameters (this is overkill, but an exact analysis is hard).
       */
      if (funcform->provolatile == PROVOLATILE_IMMUTABLE &&
          contain_mutable_functions(newexpr))
          goto fail;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eac86cc..870fae3 100644
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
*************** transformDeleteStmt(ParseState *pstate,
*** 417,422 ****
--- 417,423 ----

      qry->hasSubLinks = pstate->p_hasSubLinks;
      qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+     qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
      qry->hasAggs = pstate->p_hasAggs;
      if (pstate->p_hasAggs)
          parseCheckAggregates(pstate, qry);
*************** transformInsertStmt(ParseState *pstate,
*** 819,824 ****
--- 820,826 ----
      qry->rtable = pstate->p_rtable;
      qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

+     qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
      qry->hasSubLinks = pstate->p_hasSubLinks;

      assign_query_collations(pstate, qry);
*************** transformSelectStmt(ParseState *pstate,
*** 1231,1236 ****
--- 1233,1239 ----

      qry->hasSubLinks = pstate->p_hasSubLinks;
      qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+     qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
      qry->hasAggs = pstate->p_hasAggs;
      if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
          parseCheckAggregates(pstate, qry);
*************** transformSetOperationStmt(ParseState *ps
*** 1691,1696 ****
--- 1694,1700 ----

      qry->hasSubLinks = pstate->p_hasSubLinks;
      qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+     qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
      qry->hasAggs = pstate->p_hasAggs;
      if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
          parseCheckAggregates(pstate, qry);
*************** transformUpdateStmt(ParseState *pstate,
*** 2170,2175 ****
--- 2174,2180 ----
      qry->rtable = pstate->p_rtable;
      qry->jointree = makeFromExpr(pstate->p_joinlist, qual);

+     qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
      qry->hasSubLinks = pstate->p_hasSubLinks;

      assign_query_collations(pstate, qry);
*************** CheckSelectLocking(Query *qry, LockClaus
*** 2565,2571 ****
            translator: %s is a SQL row locking clause such as FOR UPDATE */
                   errmsg("%s is not allowed with window functions",
                          LCS_asString(strength))));
!     if (expression_returns_set((Node *) qry->targetList))
          ereport(ERROR,
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
          /*------
--- 2570,2576 ----
            translator: %s is a SQL row locking clause such as FOR UPDATE */
                   errmsg("%s is not allowed with window functions",
                          LCS_asString(strength))));
!     if (qry->hasTargetSRFs)
          ereport(ERROR,
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
          /*------
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 61af484..56c9a42 100644
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 25,30 ****
--- 25,31 ----
  #include "parser/parse_agg.h"
  #include "parser/parse_clause.h"
  #include "parser/parse_coerce.h"
+ #include "parser/parse_expr.h"
  #include "parser/parse_func.h"
  #include "parser/parse_relation.h"
  #include "parser/parse_target.h"
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 625,630 ****
--- 626,635 ----
                                        exprLocation((Node *) llast(fargs)))));
      }

+     /* if it returns a set, check that's OK */
+     if (retset)
+         check_srf_call_placement(pstate, location);
+
      /* build the appropriate output structure */
      if (fdresult == FUNCDETAIL_NORMAL)
      {
*************** LookupAggNameTypeNames(List *aggname, Li
*** 2040,2042 ****
--- 2045,2190 ----

      return oid;
  }
+
+
+ /*
+  * check_srf_call_placement
+  *        Verify that a set-returning function is called in a valid place,
+  *        and throw a nice error if not.
+  *
+  * A side-effect is to set pstate->p_hasTargetSRFs true if appropriate.
+  */
+ void
+ check_srf_call_placement(ParseState *pstate, int location)
+ {
+     const char *err;
+     bool        errkind;
+
+     /*
+      * Check to see if the set-returning function is in an invalid place
+      * within the query.  Basically, we don't allow SRFs anywhere except in
+      * the targetlist (which includes GROUP BY/ORDER BY expressions), VALUES,
+      * and functions in FROM.
+      *
+      * For brevity we support two schemes for reporting an error here: set
+      * "err" to a custom message, or set "errkind" true if the error context
+      * is sufficiently identified by what ParseExprKindName will return, *and*
+      * what it will return is just a SQL keyword.  (Otherwise, use a custom
+      * message to avoid creating translation problems.)
+      */
+     err = NULL;
+     errkind = false;
+     switch (pstate->p_expr_kind)
+     {
+         case EXPR_KIND_NONE:
+             Assert(false);        /* can't happen */
+             break;
+         case EXPR_KIND_OTHER:
+             /* Accept SRF here; caller must throw error if wanted */
+             break;
+         case EXPR_KIND_JOIN_ON:
+         case EXPR_KIND_JOIN_USING:
+             err = _("set-returning functions are not allowed in JOIN conditions");
+             break;
+         case EXPR_KIND_FROM_SUBSELECT:
+             /* can't get here, but just in case, throw an error */
+             errkind = true;
+             break;
+         case EXPR_KIND_FROM_FUNCTION:
+             /* okay ... but we can't check nesting here */
+             break;
+         case EXPR_KIND_WHERE:
+             errkind = true;
+             break;
+         case EXPR_KIND_POLICY:
+             err = _("set-returning functions are not allowed in policy expressions");
+             break;
+         case EXPR_KIND_HAVING:
+             errkind = true;
+             break;
+         case EXPR_KIND_FILTER:
+             errkind = true;
+             break;
+         case EXPR_KIND_WINDOW_PARTITION:
+         case EXPR_KIND_WINDOW_ORDER:
+             /* okay, these are effectively GROUP BY/ORDER BY */
+             pstate->p_hasTargetSRFs = true;
+             break;
+         case EXPR_KIND_WINDOW_FRAME_RANGE:
+         case EXPR_KIND_WINDOW_FRAME_ROWS:
+             err = _("set-returning functions are not allowed in window definitions");
+             break;
+         case EXPR_KIND_SELECT_TARGET:
+         case EXPR_KIND_INSERT_TARGET:
+             /* okay */
+             pstate->p_hasTargetSRFs = true;
+             break;
+         case EXPR_KIND_UPDATE_SOURCE:
+         case EXPR_KIND_UPDATE_TARGET:
+             /* disallowed because it would be ambiguous what to do */
+             errkind = true;
+             break;
+         case EXPR_KIND_GROUP_BY:
+         case EXPR_KIND_ORDER_BY:
+             /* okay */
+             pstate->p_hasTargetSRFs = true;
+             break;
+         case EXPR_KIND_DISTINCT_ON:
+             /* okay */
+             pstate->p_hasTargetSRFs = true;
+             break;
+         case EXPR_KIND_LIMIT:
+         case EXPR_KIND_OFFSET:
+             errkind = true;
+             break;
+         case EXPR_KIND_RETURNING:
+             errkind = true;
+             break;
+         case EXPR_KIND_VALUES:
+             /* okay */
+             break;
+         case EXPR_KIND_CHECK_CONSTRAINT:
+         case EXPR_KIND_DOMAIN_CHECK:
+             err = _("set-returning functions are not allowed in check constraints");
+             break;
+         case EXPR_KIND_COLUMN_DEFAULT:
+         case EXPR_KIND_FUNCTION_DEFAULT:
+             err = _("set-returning functions are not allowed in DEFAULT expressions");
+             break;
+         case EXPR_KIND_INDEX_EXPRESSION:
+             err = _("set-returning functions are not allowed in index expressions");
+             break;
+         case EXPR_KIND_INDEX_PREDICATE:
+             err = _("set-returning functions are not allowed in index predicates");
+             break;
+         case EXPR_KIND_ALTER_COL_TRANSFORM:
+             err = _("set-returning functions are not allowed in transform expressions");
+             break;
+         case EXPR_KIND_EXECUTE_PARAMETER:
+             err = _("set-returning functions are not allowed in EXECUTE parameters");
+             break;
+         case EXPR_KIND_TRIGGER_WHEN:
+             err = _("set-returning functions are not allowed in trigger WHEN conditions");
+             break;
+
+             /*
+              * There is intentionally no default: case here, so that the
+              * compiler will warn if we add a new ParseExprKind without
+              * extending this switch.  If we do see an unrecognized value at
+              * runtime, the behavior will be the same as for EXPR_KIND_OTHER,
+              * which is sane anyway.
+              */
+     }
+     if (err)
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                  errmsg_internal("%s", err),
+                  parser_errposition(pstate, location)));
+     if (errkind)
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+         /* translator: %s is name of a SQL construct, eg GROUP BY */
+                  errmsg("set-returning functions are not allowed in %s",
+                         ParseExprKindName(pstate->p_expr_kind)),
+                  parser_errposition(pstate, location)));
+ }
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index e913d05..aecda6d 100644
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
*************** make_op(ParseState *pstate, List *opname
*** 839,844 ****
--- 839,848 ----
      result->args = args;
      result->location = location;

+     /* if it returns a set, check that's OK */
+     if (result->opretset)
+         check_srf_call_placement(pstate, location);
+
      ReleaseSysCache(tup);

      return (Expr *) result;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7a2950e..eaffc49 100644
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
*************** transformIndexStmt(Oid relid, IndexStmt
*** 2106,2122 ****

              /*
               * transformExpr() should have already rejected subqueries,
!              * aggregates, and window functions, based on the EXPR_KIND_ for
!              * an index expression.
               *
-              * Also reject expressions returning sets; this is for consistency
-              * with what transformWhereClause() checks for the predicate.
               * DefineIndex() will make more checks.
               */
-             if (expression_returns_set(ielem->expr))
-                 ereport(ERROR,
-                         (errcode(ERRCODE_DATATYPE_MISMATCH),
-                          errmsg("index expression cannot return a set")));
          }
      }

--- 2106,2116 ----

              /*
               * transformExpr() should have already rejected subqueries,
!              * aggregates, window functions, and SRFs, based on the EXPR_KIND_
!              * for an index expression.
               *
               * DefineIndex() will make more checks.
               */
          }
      }

*************** transformAlterTableStmt(Oid relid, Alter
*** 2594,2605 ****
                          def->cooked_default =
                              transformExpr(pstate, def->raw_default,
                                            EXPR_KIND_ALTER_COL_TRANSFORM);
-
-                         /* it can't return a set */
-                         if (expression_returns_set(def->cooked_default))
-                             ereport(ERROR,
-                                     (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                      errmsg("transform expression must not return a set")));
                      }

                      newcmds = lappend(newcmds, cmd);
--- 2588,2593 ----
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..b828e3c 100644
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** view_query_is_auto_updatable(Query *view
*** 2221,2227 ****
      if (viewquery->hasWindowFuncs)
          return gettext_noop("Views that return window functions are not automatically updatable.");

!     if (expression_returns_set((Node *) viewquery->targetList))
          return gettext_noop("Views that return set-returning functions are not automatically updatable.");

      /*
--- 2221,2227 ----
      if (viewquery->hasWindowFuncs)
          return gettext_noop("Views that return window functions are not automatically updatable.");

!     if (viewquery->hasTargetSRFs)
          return gettext_noop("Views that return set-returning functions are not automatically updatable.");

      /*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8d3dcf4..6de2cab 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct Query
*** 116,121 ****
--- 116,122 ----

      bool        hasAggs;        /* has aggregates in tlist or havingQual */
      bool        hasWindowFuncs; /* has window functions in tlist */
+     bool        hasTargetSRFs;    /* has set-returning functions in tlist */
      bool        hasSubLinks;    /* has subquery SubLink */
      bool        hasDistinctOn;    /* distinctClause is from DISTINCT ON */
      bool        hasRecursive;    /* WITH RECURSIVE was specified */
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 0cefdf1..ed16d36 100644
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
*************** extern Oid LookupFuncNameTypeNames(List
*** 67,70 ****
--- 67,72 ----
  extern Oid LookupAggNameTypeNames(List *aggname, List *argtypes,
                         bool noError);

+ extern void check_srf_call_placement(ParseState *pstate, int location);
+
  #endif   /* PARSE_FUNC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..6633586 100644
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 27,33 ****
   * by extension code that might need to call transformExpr().  The core code
   * will not enforce any context-driven restrictions on EXPR_KIND_OTHER
   * expressions, so the caller would have to check for sub-selects, aggregates,
!  * and window functions if those need to be disallowed.
   */
  typedef enum ParseExprKind
  {
--- 27,33 ----
   * by extension code that might need to call transformExpr().  The core code
   * will not enforce any context-driven restrictions on EXPR_KIND_OTHER
   * expressions, so the caller would have to check for sub-selects, aggregates,
!  * window functions, SRFs, etc if those need to be disallowed.
   */
  typedef enum ParseExprKind
  {
*************** struct ParseState
*** 150,155 ****
--- 150,156 ----
      Node       *p_value_substitute;        /* what to replace VALUE with, if any */
      bool        p_hasAggs;
      bool        p_hasWindowFuncs;
+     bool        p_hasTargetSRFs;
      bool        p_hasSubLinks;
      bool        p_hasModifyingCTE;
      bool        p_is_insert;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 6141b7a..470cf93 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** exec_simple_check_plan(PLpgSQL_execstate
*** 6799,6804 ****
--- 6799,6805 ----
       */
      if (query->hasAggs ||
          query->hasWindowFuncs ||
+         query->hasTargetSRFs ||
          query->hasSubLinks ||
          query->hasForUpdate ||
          query->cteList ||
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index 805e8db..622f755 100644
*** a/src/test/regress/expected/tsrf.out
--- b/src/test/regress/expected/tsrf.out
*************** SELECT * FROM fewmore;
*** 359,373 ****
      5
  (5 rows)

! -- nonsense that seems to be allowed
  UPDATE fewmore SET data = generate_series(4,9);
  -- SRFs are not allowed in RETURNING
  INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
! ERROR:  set-valued function called in context that cannot accept a set
  -- nor aggregate arguments
  SELECT count(generate_series(1,3)) FROM few;
  ERROR:  set-valued function called in context that cannot accept a set
! -- nor proper VALUES
  VALUES(1, generate_series(1,2));
  ERROR:  set-valued function called in context that cannot accept a set
  -- DISTINCT ON is evaluated before tSRF evaluation if SRF is not
--- 359,378 ----
      5
  (5 rows)

! -- SRFs are not allowed in UPDATE (they once were, but it was nonsense)
  UPDATE fewmore SET data = generate_series(4,9);
+ ERROR:  set-returning functions are not allowed in UPDATE
+ LINE 1: UPDATE fewmore SET data = generate_series(4,9);
+                                   ^
  -- SRFs are not allowed in RETURNING
  INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
! ERROR:  set-returning functions are not allowed in RETURNING
! LINE 1: INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3)...
!                                                 ^
  -- nor aggregate arguments
  SELECT count(generate_series(1,3)) FROM few;
  ERROR:  set-valued function called in context that cannot accept a set
! -- nor standalone VALUES (but surely this is a bug?)
  VALUES(1, generate_series(1,2));
  ERROR:  set-valued function called in context that cannot accept a set
  -- DISTINCT ON is evaluated before tSRF evaluation if SRF is not
*************** SELECT a, generate_series(1,2) FROM (VAL
*** 457,463 ****

  -- SRFs are not allowed in LIMIT.
  SELECT 1 LIMIT generate_series(1,3);
! ERROR:  argument of LIMIT must not return a set
  LINE 1: SELECT 1 LIMIT generate_series(1,3);
                         ^
  -- tSRF in correlated subquery, referencing table outside
--- 462,468 ----

  -- SRFs are not allowed in LIMIT.
  SELECT 1 LIMIT generate_series(1,3);
! ERROR:  set-returning functions are not allowed in LIMIT
  LINE 1: SELECT 1 LIMIT generate_series(1,3);
                         ^
  -- tSRF in correlated subquery, referencing table outside
diff --git a/src/test/regress/sql/tsrf.sql b/src/test/regress/sql/tsrf.sql
index 5247795..c28dd01 100644
*** a/src/test/regress/sql/tsrf.sql
--- b/src/test/regress/sql/tsrf.sql
*************** CREATE TABLE fewmore AS SELECT generate_
*** 68,81 ****
  INSERT INTO fewmore VALUES(generate_series(4,5));
  SELECT * FROM fewmore;

! -- nonsense that seems to be allowed
  UPDATE fewmore SET data = generate_series(4,9);

  -- SRFs are not allowed in RETURNING
  INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
  -- nor aggregate arguments
  SELECT count(generate_series(1,3)) FROM few;
! -- nor proper VALUES
  VALUES(1, generate_series(1,2));

  -- DISTINCT ON is evaluated before tSRF evaluation if SRF is not
--- 68,81 ----
  INSERT INTO fewmore VALUES(generate_series(4,5));
  SELECT * FROM fewmore;

! -- SRFs are not allowed in UPDATE (they once were, but it was nonsense)
  UPDATE fewmore SET data = generate_series(4,9);

  -- SRFs are not allowed in RETURNING
  INSERT INTO fewmore VALUES(1) RETURNING generate_series(1,3);
  -- nor aggregate arguments
  SELECT count(generate_series(1,3)) FROM few;
! -- nor standalone VALUES (but surely this is a bug?)
  VALUES(1, generate_series(1,2));

  -- DISTINCT ON is evaluated before tSRF evaluation if SRF is not


On September 13, 2016 9:07:35 AM PDT, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>Andres Freund <andres@anarazel.de> writes:
>> Attached is a significantly updated patch series (see the mail one up
>> for details about what this is, I don't want to quote it in its
>> entirety).
>
>I've reviewed the portions of 0005 that have to do with making the
>parser
>mark queries with hasTargetSRF.  The code as you had it was wrong
>because
>it would set the flag as a consequence of SRFs in function RTEs, which
>we don't want.

I'd taken it more as the possibility that there's an srf, than guarantee so far. There might be cases where the planner
removesthe srf during folding or such.  Makes sense to make it more accurate.
 


>  It seemed to me that the best way to fix that was to
>rely
>on the parser's p_expr_kind mechanism to tell which part of the query
>we're in, whereupon we might as well make the parser act more like it
>does
>for aggregates and window functions and give a suitable error at parse
>time for misplaced SRFs.

That's a nice improvement. The execution time errors are ugly.

>I also renamed the flag to hasTargetSRFs, which is more parallel to
>hasAggs and hasWindowFuncs, and made some effort to use it in place
>of expression_returns_set() searches.
>
>I'd like to go ahead and push this, since it's a necessary prerequisite
>for either approach we might adopt for the rest of the patch series,
>and the improved error reporting and avoidance of expensive
>expression_returns_set searches make it a win IMO even if we were not
>planning to do anything more with SRFs.

Can't look are the code just now, on my way to the airport for pgopen, but the idea sounds good to me.

Andres
-- 
Sent from my Android device with K-9 Mail. Please excuse my brevity.



Andres Freund <andres@anarazel.de> writes:
> On September 13, 2016 9:07:35 AM PDT, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I'd like to go ahead and push this, since it's a necessary prerequisite
>> for either approach we might adopt for the rest of the patch series,
>> and the improved error reporting and avoidance of expensive
>> expression_returns_set searches make it a win IMO even if we were not
>> planning to do anything more with SRFs.

> Can't look are the code just now, on my way to the airport for pgopen, but the idea sounds good to me.

OK, I went ahead and pushed it.  We can tweak later if needed.
        regards, tom lane



On 2016-09-13 12:07:35 -0400, Tom Lane wrote:
> diff --git a/src/backend/optimizer/plan/analindex e28a8dc..74e4245 100644
> --- a/src/backend/optimizer/plan/analyzejoins.c
> +++ b/src/backend/optimizer/plan/analyzejoins.c
> @@ -650,6 +650,11 @@ rel_is_distinct_for(PlannerInfo *root, R
>  bool
>  query_supports_distinctness(Query *query)
>  {
> +    /* we don't cope with SRFs, see comment below */
> +    if (query->hasTargetSRFs)
> +        return false;
> +
> +    /* check for features we can prove distinctness with */
>      if (query->distinctClause != NIL ||
>          query->groupClause != NIL ||
>          query->groupingSets != NIL ||
> @@ -695,7 +700,7 @@ query_is_distinct_for(Query *query, List
>       * specified columns, since those must be evaluated before de-duplication;
>       * but it doesn't presently seem worth the complication to check that.)
>       */
> -    if (expression_returns_set((Node *) query->targetList))
> +    if (query->hasTargetSRFs)
>          return false;

Maybe make this hasTargetSRFs && expression_returns_set()? The SRF could
have been optimized away. (Oh, I see you recheck below. Forget that then).

> @@ -1419,8 +1419,8 @@ is_simple_subquery(Query *subquery, Rang
>          return false;
>  
>      /*
> -     * Can't pull up a subquery involving grouping, aggregation, sorting,
> -     * limiting, or WITH.  (XXX WITH could possibly be allowed later)
> +     * Can't pull up a subquery involving grouping, aggregation, SRFs,
> +     * sorting, limiting, or WITH.  (XXX WITH could possibly be allowed later)
>       *
>       * We also don't pull up a subquery that has explicit FOR UPDATE/SHARE
>       * clauses, because pullup would cause the locking to occur semantically
> @@ -1430,6 +1430,7 @@ is_simple_subquery(Query *subquery, Rang
>       */
>      if (subquery->hasAggs ||
>          subquery->hasWindowFuncs ||
> +        subquery->hasTargetSRFs ||
>          subquery->groupClause ||
>          subquery->groupingSets ||
>          subquery->havingQual ||
> @@ -1543,15 +1544,6 @@ is_simple_subquery(Query *subquery, Rang
>      }
>  
>      /*
> -     * Don't pull up a subquery that has any set-returning functions in its
> -     * targetlist.  Otherwise we might well wind up inserting set-returning
> -     * functions into places where they mustn't go, such as quals of higher
> -     * queries.  This also ensures deletion of an empty jointree is valid.
> -     */
> -    if (expression_returns_set((Node *) subquery->targetList))
> -        return false;

I don't quite understand parts of the comment you removed here. What
does "This also ensures deletion of an empty jointree is valid." mean?


Looks good, except that you didn't adopt the hunk adjusting
src/backend/executor/README, which still seems to read:
> We disallow set-returning functions in the targetlist of SELECT FOR UPDATE,
> so as to ensure that at most one tuple can be returned for any particular
> set of scan tuples.  Otherwise we'd get duplicates due to the original
> query returning the same set of scan tuples multiple times.  (Note: there
> is no explicit prohibition on SRFs in UPDATE, but the net effect will be
> that only the first result row of an SRF counts, because all subsequent
> rows will result in attempts to re-update an already updated target row.
> This is historical behavior and seems not worth changing.)

Regards,

Andres



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-13 12:07:35 -0400, Tom Lane wrote:
>> /*
>> -     * Don't pull up a subquery that has any set-returning functions in its
>> -     * targetlist.  Otherwise we might well wind up inserting set-returning
>> -     * functions into places where they mustn't go, such as quals of higher
>> -     * queries.  This also ensures deletion of an empty jointree is valid.
>> -     */
>> -    if (expression_returns_set((Node *) subquery->targetList))
>> -        return false;

> I don't quite understand parts of the comment you removed here. What
> does "This also ensures deletion of an empty jointree is valid." mean?

TBH, I don't remember what that was about anymore.  Whatever it was might
not apply now, anyway.  If there was something to it, maybe we'll
rediscover it while we're fooling with tSRFs, and then we can insert a
less cryptic comment.

> Looks good, except that you didn't adopt the hunk adjusting
> src/backend/executor/README, which still seems to read:

Ah, I missed that there was anything to change docs-wise.  Will fix.

Thanks for looking it over!
        regards, tom lane



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-12 19:35:22 -0400, Tom Lane wrote:
>> Anyway I'll draft a prototype and then we can compare.

> Ok, cool.

Here's a draft patch that is just meant to investigate what the planner
changes might look like if we do it in the pipelined-result way.
Accordingly, I didn't touch the executor, but just had it emit regular
Result nodes for SRF-execution steps.  However, the SRFs are all
guaranteed to appear at top level of their respective tlists, so that
those Results could be replaced with something that works like
nodeFunctionscan.

A difficulty with this restriction is that if you have a query like
"select f1, generate_series(1,2) / 10 from tab" then you end up with both
a SRF-executing Result and a separate scalar-projection Result above it,
because the division-by-ten has to happen in a separate plan level.
The planner's notions about the cost of Result make it think that this is
quite expensive --- mainly because the upper Result will be iterated once
per SRF output row, so that you get hit with cpu_tuple_cost per output row.
And that in turn leads it to change plans in one or two cases in the
regression tests.  Maybe that's fine.  I'm worried though that it's right
that this will be unduly expensive.  So I'm kind of tempted to define the
SRF-executing node as acting more like, say, Agg or WindowFunc, in that
it has a list of SRFs to execute and then it has the ability to project a
scalar tlist on top of those results.  That would likely save some cycles
at execution, and it would also eliminate a few of the planner warts seen
below, like the rule about not pushing a new scalar tlist down onto a
SRF-executing Result.  I'd have to rewrite split_pathtarget_at_srfs(),
because it'd be implementing quite different rules about how to refactor
targetlists, but that's not a big problem.

On the whole I'm pretty pleased with this approach, at least from the
point of view of the planner.  The net addition of planner code is
smaller than what you had, and though I'm no doubt biased, I think this
version is much cleaner.  Also, though this patch doesn't address exactly
how we might do it, it's fairly clear that it'd be possible to allow
FDWs and CustomScans to implement SRF execution, eg pushing a SRF down to
a foreign server, in a reasonably straightforward extension of the
existing upper-pathification hooks.  If we go with the lateral function
RTE approach, that's going to be somewhere between hard and impossible.

So I think we should continue investigating this way of doing things.
I'll try to take a look at the executor end of it tomorrow.  However
I'm leaving Friday for a week's vacation, and may not have anything to
show before that.

            regards, tom lane

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7e092d7..9052273 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outProjectionPath(StringInfo str, const
*** 1817,1822 ****
--- 1817,1823 ----

      WRITE_NODE_FIELD(subpath);
      WRITE_BOOL_FIELD(dummypp);
+     WRITE_BOOL_FIELD(srfpp);
  }

  static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..7c59c3d 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** create_projection_plan(PlannerInfo *root
*** 1421,1428 ****
      Plan       *subplan;
      List       *tlist;

!     /* Since we intend to project, we don't need to constrain child tlist */
!     subplan = create_plan_recurse(root, best_path->subpath, 0);

      tlist = build_path_tlist(root, &best_path->path);

--- 1421,1441 ----
      Plan       *subplan;
      List       *tlist;

!     /*
!      * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath,
!      * don't bother with it, just make a Result with no input.  This avoids an
!      * extra Result plan node when doing "SELECT srf()".  Depending on what we
!      * decide about the desired plan structure for SRF-expanding nodes, this
!      * optimization might have to go away, and in any case it'll probably look
!      * a good bit different.
!      */
!     if (IsA(best_path->subpath, ResultPath) &&
!         ((ResultPath *) best_path->subpath)->path.pathtarget->exprs == NIL &&
!         ((ResultPath *) best_path->subpath)->quals == NIL)
!         subplan = NULL;
!     else
!         /* Since we intend to project, we don't need to constrain child tlist */
!         subplan = create_plan_recurse(root, best_path->subpath, 0);

      tlist = build_path_tlist(root, &best_path->path);

*************** create_projection_plan(PlannerInfo *root
*** 1441,1448 ****
       * creation, but that would add expense to creating Paths we might end up
       * not using.)
       */
!     if (is_projection_capable_path(best_path->subpath) ||
!         tlist_same_exprs(tlist, subplan->targetlist))
      {
          /* Don't need a separate Result, just assign tlist to subplan */
          plan = subplan;
--- 1454,1462 ----
       * creation, but that would add expense to creating Paths we might end up
       * not using.)
       */
!     if (!best_path->srfpp &&
!         (is_projection_capable_path(best_path->subpath) ||
!          tlist_same_exprs(tlist, subplan->targetlist)))
      {
          /* Don't need a separate Result, just assign tlist to subplan */
          plan = subplan;
*************** is_projection_capable_path(Path *path)
*** 6185,6190 ****
--- 6199,6215 ----
               * projection to its dummy path.
               */
              return IS_DUMMY_PATH(path);
+         case T_Result:
+
+             /*
+              * If the path is doing SRF evaluation, claim it can't project, so
+              * we don't jam a new tlist into it and thereby break the property
+              * that the SRFs appear at top level.
+              */
+             if (IsA(path, ProjectionPath) &&
+                 ((ProjectionPath *) path)->srfpp)
+                 return false;
+             break;
          default:
              break;
      }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f657ffc..8fff294 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static List *make_pathkeys_for_window(Pl
*** 153,158 ****
--- 153,160 ----
  static PathTarget *make_sort_input_target(PlannerInfo *root,
                         PathTarget *final_target,
                         bool *have_postponed_srfs);
+ static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
+                       List *targets, List *targets_contain_srfs);


  /*****************************************************************************
*************** grouping_planner(PlannerInfo *root, bool
*** 1440,1447 ****
      int64        count_est = 0;
      double        limit_tuples = -1.0;
      bool        have_postponed_srfs = false;
-     double        tlist_rows;
      PathTarget *final_target;
      RelOptInfo *current_rel;
      RelOptInfo *final_rel;
      ListCell   *lc;
--- 1442,1450 ----
      int64        count_est = 0;
      double        limit_tuples = -1.0;
      bool        have_postponed_srfs = false;
      PathTarget *final_target;
+     List       *final_targets;
+     List       *final_targets_contain_srfs;
      RelOptInfo *current_rel;
      RelOptInfo *final_rel;
      ListCell   *lc;
*************** grouping_planner(PlannerInfo *root, bool
*** 1504,1509 ****
--- 1507,1516 ----
          /* Also extract the PathTarget form of the setop result tlist */
          final_target = current_rel->cheapest_total_path->pathtarget;

+         /* The setop result tlist couldn't contain any SRFs */
+         Assert(!parse->hasTargetSRFs);
+         final_targets = final_targets_contain_srfs = NIL;
+
          /*
           * Can't handle FOR [KEY] UPDATE/SHARE here (parser should have
           * checked already, but let's make sure).
*************** grouping_planner(PlannerInfo *root, bool
*** 1529,1536 ****
--- 1536,1549 ----
      {
          /* No set operations, do regular planning */
          PathTarget *sort_input_target;
+         List       *sort_input_targets;
+         List       *sort_input_targets_contain_srfs;
          PathTarget *grouping_target;
+         List       *grouping_targets;
+         List       *grouping_targets_contain_srfs;
          PathTarget *scanjoin_target;
+         List       *scanjoin_targets;
+         List       *scanjoin_targets_contain_srfs;
          bool        have_grouping;
          AggClauseCosts agg_costs;
          WindowFuncLists *wflists = NULL;
*************** grouping_planner(PlannerInfo *root, bool
*** 1781,1788 ****
              scanjoin_target = grouping_target;

          /*
!          * Forcibly apply scan/join target to all the Paths for the scan/join
!          * rel.
           *
           * In principle we should re-run set_cheapest() here to identify the
           * cheapest path, but it seems unlikely that adding the same tlist
--- 1794,1843 ----
              scanjoin_target = grouping_target;

          /*
!          * If there are any SRFs in the targetlist, we must separate each of
!          * these PathTargets into SRF-computing and SRF-free targets.  Replace
!          * each of the named targets with a SRF-free version, and remember the
!          * list of additional projection steps we need to add afterwards.
!          */
!         if (parse->hasTargetSRFs)
!         {
!             /* final_target doesn't recompute any SRFs in sort_input_target */
!             split_pathtarget_at_srfs(root, final_target, sort_input_target,
!                                      &final_targets,
!                                      &final_targets_contain_srfs);
!             final_target = (PathTarget *) linitial(final_targets);
!             Assert(!linitial_int(final_targets_contain_srfs));
!             /* likewise for sort_input_target vs. grouping_target */
!             split_pathtarget_at_srfs(root, sort_input_target, grouping_target,
!                                      &sort_input_targets,
!                                      &sort_input_targets_contain_srfs);
!             sort_input_target = (PathTarget *) linitial(sort_input_targets);
!             Assert(!linitial_int(sort_input_targets_contain_srfs));
!             /* likewise for grouping_target vs. scanjoin_target */
!             split_pathtarget_at_srfs(root, grouping_target, scanjoin_target,
!                                      &grouping_targets,
!                                      &grouping_targets_contain_srfs);
!             grouping_target = (PathTarget *) linitial(grouping_targets);
!             Assert(!linitial_int(grouping_targets_contain_srfs));
!             /* scanjoin_target will not have any SRFs precomputed for it */
!             split_pathtarget_at_srfs(root, scanjoin_target, NULL,
!                                      &scanjoin_targets,
!                                      &scanjoin_targets_contain_srfs);
!             scanjoin_target = (PathTarget *) linitial(scanjoin_targets);
!             Assert(!linitial_int(scanjoin_targets_contain_srfs));
!         }
!         else
!         {
!             /* initialize lists, just to keep compiler quiet */
!             final_targets = final_targets_contain_srfs = NIL;
!             sort_input_targets = sort_input_targets_contain_srfs = NIL;
!             grouping_targets = grouping_targets_contain_srfs = NIL;
!             scanjoin_targets = scanjoin_targets_contain_srfs = NIL;
!         }
!
!         /*
!          * Forcibly apply SRF-free scan/join target to all the Paths for the
!          * scan/join rel.
           *
           * In principle we should re-run set_cheapest() here to identify the
           * cheapest path, but it seems unlikely that adding the same tlist
*************** grouping_planner(PlannerInfo *root, bool
*** 1853,1858 ****
--- 1908,1919 ----
              current_rel->partial_pathlist = NIL;
          }

+         /* Now fix things up if scan/join target contains SRFs */
+         if (parse->hasTargetSRFs)
+             adjust_paths_for_srfs(root, current_rel,
+                                   scanjoin_targets,
+                                   scanjoin_targets_contain_srfs);
+
          /*
           * Save the various upper-rel PathTargets we just computed into
           * root->upper_targets[].  The core code doesn't use this, but it
*************** grouping_planner(PlannerInfo *root, bool
*** 1877,1882 ****
--- 1938,1948 ----
                                                  &agg_costs,
                                                  rollup_lists,
                                                  rollup_groupclauses);
+             /* Fix things up if grouping_target contains SRFs */
+             if (parse->hasTargetSRFs)
+                 adjust_paths_for_srfs(root, current_rel,
+                                       grouping_targets,
+                                       grouping_targets_contain_srfs);
          }

          /*
*************** grouping_planner(PlannerInfo *root, bool
*** 1892,1897 ****
--- 1958,1968 ----
                                                tlist,
                                                wflists,
                                                activeWindows);
+             /* Fix things up if sort_input_target contains SRFs */
+             if (parse->hasTargetSRFs)
+                 adjust_paths_for_srfs(root, current_rel,
+                                       sort_input_targets,
+                                       sort_input_targets_contain_srfs);
          }

          /*
*************** grouping_planner(PlannerInfo *root, bool
*** 1920,1959 ****
                                             final_target,
                                             have_postponed_srfs ? -1.0 :
                                             limit_tuples);
!     }
!
!     /*
!      * If there are set-returning functions in the tlist, scale up the output
!      * rowcounts of all surviving Paths to account for that.  Note that if any
!      * SRFs appear in sorting or grouping columns, we'll have underestimated
!      * the numbers of rows passing through earlier steps; but that's such a
!      * weird usage that it doesn't seem worth greatly complicating matters to
!      * account for it.
!      */
!     if (parse->hasTargetSRFs)
!         tlist_rows = tlist_returns_set_rows(tlist);
!     else
!         tlist_rows = 1;
!
!     if (tlist_rows > 1)
!     {
!         foreach(lc, current_rel->pathlist)
!         {
!             Path       *path = (Path *) lfirst(lc);
!
!             /*
!              * We assume that execution costs of the tlist as such were
!              * already accounted for.  However, it still seems appropriate to
!              * charge something more for the executor's general costs of
!              * processing the added tuples.  The cost is probably less than
!              * cpu_tuple_cost, though, so we arbitrarily use half of that.
!              */
!             path->total_cost += path->rows * (tlist_rows - 1) *
!                 cpu_tuple_cost / 2;
!
!             path->rows *= tlist_rows;
!         }
!         /* No need to run set_cheapest; we're keeping all paths anyway. */
      }

      /*
--- 1991,2001 ----
                                             final_target,
                                             have_postponed_srfs ? -1.0 :
                                             limit_tuples);
!         /* Fix things up if final_target contains SRFs */
!         if (parse->hasTargetSRFs)
!             adjust_paths_for_srfs(root, current_rel,
!                                   final_targets,
!                                   final_targets_contain_srfs);
      }

      /*
*************** get_cheapest_fractional_path(RelOptInfo
*** 5151,5156 ****
--- 5193,5301 ----
  }

  /*
+  * adjust_paths_for_srfs
+  *        Fix up the Paths of the given upperrel to handle tSRFs properly.
+  *
+  * The executor can only handle set-returning functions that appear at the
+  * top level of the targetlist of a Result plan node.  If we have any SRFs
+  * that are not at top level, we need to split up the evaluation into multiple
+  * plan levels in which each level satisfies this constraint.  This function
+  * modifies each Path of an upperrel that (might) compute any SRFs in its
+  * output tlist to insert appropriate projection steps.
+  *
+  * The given targets and targets_contain_srfs lists are from
+  * split_pathtarget_at_srfs().  We assume the existing Paths emit the first
+  * target in targets.
+  */
+ static void
+ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
+                       List *targets, List *targets_contain_srfs)
+ {
+     ListCell   *lc;
+
+     Assert(list_length(targets) == list_length(targets_contain_srfs));
+     Assert(!linitial_int(targets_contain_srfs));
+
+     /* If no SRFs appear at this plan level, nothing to do */
+     if (list_length(targets) == 1)
+         return;
+
+     /*
+      * Stack SRF-evaluation nodes atop each path for the rel.
+      *
+      * In principle we should re-run set_cheapest() here to identify the
+      * cheapest path, but it seems unlikely that adding the same tlist eval
+      * costs to all the paths would change that, so we don't bother. Instead,
+      * just assume that the cheapest-startup and cheapest-total paths remain
+      * so.  (There should be no parameterized paths anymore, so we needn't
+      * worry about updating cheapest_parameterized_paths.)
+      */
+     foreach(lc, rel->pathlist)
+     {
+         Path       *subpath = (Path *) lfirst(lc);
+         Path       *newpath = subpath;
+         ListCell   *lc1,
+                    *lc2;
+
+         Assert(subpath->param_info == NULL);
+         forboth(lc1, targets, lc2, targets_contain_srfs)
+         {
+             PathTarget *thistarget = (PathTarget *) lfirst(lc1);
+             bool        contains_srfs = (bool) lfirst_int(lc2);
+
+             /* If this level doesn't contain SRFs, do regular projection */
+             if (contains_srfs)
+                 newpath = (Path *) create_srf_projection_path(root,
+                                                               rel,
+                                                               newpath,
+                                                               thistarget);
+             else
+                 newpath = (Path *) apply_projection_to_path(root,
+                                                             rel,
+                                                             newpath,
+                                                             thistarget);
+         }
+         lfirst(lc) = newpath;
+         if (subpath == rel->cheapest_startup_path)
+             rel->cheapest_startup_path = newpath;
+         if (subpath == rel->cheapest_total_path)
+             rel->cheapest_total_path = newpath;
+     }
+
+     /* Likewise for partial paths, if any */
+     foreach(lc, rel->partial_pathlist)
+     {
+         Path       *subpath = (Path *) lfirst(lc);
+         Path       *newpath = subpath;
+         ListCell   *lc1,
+                    *lc2;
+
+         Assert(subpath->param_info == NULL);
+         forboth(lc1, targets, lc2, targets_contain_srfs)
+         {
+             PathTarget *thistarget = (PathTarget *) lfirst(lc1);
+             bool        contains_srfs = (bool) lfirst_int(lc2);
+
+             /* If this level doesn't contain SRFs, do regular projection */
+             if (contains_srfs)
+                 newpath = (Path *) create_srf_projection_path(root,
+                                                               rel,
+                                                               newpath,
+                                                               thistarget);
+             else
+             {
+                 /* avoid apply_projection_to_path, in case of multiple refs */
+                 newpath = (Path *) create_projection_path(root,
+                                                           rel,
+                                                           newpath,
+                                                           thistarget);
+             }
+         }
+         lfirst(lc) = newpath;
+     }
+ }
+
+ /*
   * expression_planner
   *        Perform planner's transformations on a standalone expression.
   *
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 663ffe0..0aa4339 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** static bool contain_agg_clause_walker(No
*** 99,105 ****
  static bool get_agg_clause_costs_walker(Node *node,
                              get_agg_clause_costs_context *context);
  static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
- static bool expression_returns_set_rows_walker(Node *node, double *count);
  static bool contain_subplans_walker(Node *node, void *context);
  static bool contain_mutable_functions_walker(Node *node, void *context);
  static bool contain_volatile_functions_walker(Node *node, void *context);
--- 99,104 ----
*************** find_window_functions_walker(Node *node,
*** 780,893 ****
  /*
   * expression_returns_set_rows
   *      Estimate the number of rows returned by a set-returning expression.
!  *      The result is 1 if there are no set-returning functions.
   *
!  * We use the product of the rowcount estimates of all the functions in
!  * the given tree (this corresponds to the behavior of ExecMakeFunctionResult
!  * for nested set-returning functions).
   *
   * Note: keep this in sync with expression_returns_set() in nodes/nodeFuncs.c.
   */
  double
  expression_returns_set_rows(Node *clause)
  {
!     double        result = 1;
!
!     (void) expression_returns_set_rows_walker(clause, &result);
!     return clamp_row_est(result);
! }
!
! static bool
! expression_returns_set_rows_walker(Node *node, double *count)
! {
!     if (node == NULL)
!         return false;
!     if (IsA(node, FuncExpr))
      {
!         FuncExpr   *expr = (FuncExpr *) node;

          if (expr->funcretset)
!             *count *= get_func_rows(expr->funcid);
      }
!     if (IsA(node, OpExpr))
      {
!         OpExpr       *expr = (OpExpr *) node;

          if (expr->opretset)
          {
              set_opfuncid(expr);
!             *count *= get_func_rows(expr->opfuncid);
          }
      }
!
!     /* Avoid recursion for some cases that can't return a set */
!     if (IsA(node, Aggref))
!         return false;
!     if (IsA(node, WindowFunc))
!         return false;
!     if (IsA(node, DistinctExpr))
!         return false;
!     if (IsA(node, NullIfExpr))
!         return false;
!     if (IsA(node, ScalarArrayOpExpr))
!         return false;
!     if (IsA(node, BoolExpr))
!         return false;
!     if (IsA(node, SubLink))
!         return false;
!     if (IsA(node, SubPlan))
!         return false;
!     if (IsA(node, AlternativeSubPlan))
!         return false;
!     if (IsA(node, ArrayExpr))
!         return false;
!     if (IsA(node, RowExpr))
!         return false;
!     if (IsA(node, RowCompareExpr))
!         return false;
!     if (IsA(node, CoalesceExpr))
!         return false;
!     if (IsA(node, MinMaxExpr))
!         return false;
!     if (IsA(node, XmlExpr))
!         return false;
!
!     return expression_tree_walker(node, expression_returns_set_rows_walker,
!                                   (void *) count);
! }
!
! /*
!  * tlist_returns_set_rows
!  *      Estimate the number of rows returned by a set-returning targetlist.
!  *      The result is 1 if there are no set-returning functions.
!  *
!  * Here, the result is the largest rowcount estimate of any of the tlist's
!  * expressions, not the product as you would get from naively applying
!  * expression_returns_set_rows() to the whole tlist.  The behavior actually
!  * implemented by ExecTargetList produces a number of rows equal to the least
!  * common multiple of the expression rowcounts, so that the product would be
!  * a worst-case estimate that is typically not realistic.  Taking the max as
!  * we do here is a best-case estimate that might not be realistic either,
!  * but it's probably closer for typical usages.  We don't try to compute the
!  * actual LCM because we're working with very approximate estimates, so their
!  * LCM would be unduly noisy.
!  */
! double
! tlist_returns_set_rows(List *tlist)
! {
!     double        result = 1;
!     ListCell   *lc;
!
!     foreach(lc, tlist)
!     {
!         TargetEntry *tle = (TargetEntry *) lfirst(lc);
!         double        colresult;
!
!         colresult = expression_returns_set_rows((Node *) tle->expr);
!         if (result < colresult)
!             result = colresult;
!     }
!     return result;
  }


--- 779,815 ----
  /*
   * expression_returns_set_rows
   *      Estimate the number of rows returned by a set-returning expression.
!  *      The result is 1 if it's not a set-returning expression.
   *
!  * We should only examine the top-level function or operator; it used to be
!  * appropriate to recurse, but not anymore.  (Even if there are more SRFs in
!  * the function's inputs, their multipliers are accounted for separately.)
   *
   * Note: keep this in sync with expression_returns_set() in nodes/nodeFuncs.c.
   */
  double
  expression_returns_set_rows(Node *clause)
  {
!     if (clause == NULL)
!         return 1.0;
!     if (IsA(clause, FuncExpr))
      {
!         FuncExpr   *expr = (FuncExpr *) clause;

          if (expr->funcretset)
!             return clamp_row_est(get_func_rows(expr->funcid));
      }
!     if (IsA(clause, OpExpr))
      {
!         OpExpr       *expr = (OpExpr *) clause;

          if (expr->opretset)
          {
              set_opfuncid(expr);
!             return clamp_row_est(get_func_rows(expr->opfuncid));
          }
      }
!     return 1.0;
  }


diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index abb7507..5a7891f 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** create_projection_path(PlannerInfo *root
*** 2227,2232 ****
--- 2227,2235 ----
              (cpu_tuple_cost + target->cost.per_tuple) * subpath->rows;
      }

+     /* Assume no SRFs around */
+     pathnode->srfpp = false;
+
      return pathnode;
  }

*************** apply_projection_to_path(PlannerInfo *ro
*** 2320,2325 ****
--- 2323,2400 ----
  }

  /*
+  * create_srf_projection_path
+  *      Creates a pathnode that represents performing a SRF projection.
+  *
+  * For the moment, we just use ProjectionPath for this, and generate a
+  * Result plan node.  That's likely to change.
+  *
+  * 'rel' is the parent relation associated with the result
+  * 'subpath' is the path representing the source of data
+  * 'target' is the PathTarget to be computed
+  */
+ ProjectionPath *
+ create_srf_projection_path(PlannerInfo *root,
+                            RelOptInfo *rel,
+                            Path *subpath,
+                            PathTarget *target)
+ {
+     ProjectionPath *pathnode = makeNode(ProjectionPath);
+     double        tlist_rows;
+     ListCell   *lc;
+
+     pathnode->path.pathtype = T_Result;
+     pathnode->path.parent = rel;
+     pathnode->path.pathtarget = target;
+     /* For now, assume we are above any joins, so no parameterization */
+     pathnode->path.param_info = NULL;
+     pathnode->path.parallel_aware = false;
+     pathnode->path.parallel_safe = rel->consider_parallel &&
+         subpath->parallel_safe &&
+         is_parallel_safe(root, (Node *) target->exprs);
+     pathnode->path.parallel_workers = subpath->parallel_workers;
+     /* Projection does not change the sort order */
+     pathnode->path.pathkeys = subpath->pathkeys;
+
+     pathnode->subpath = subpath;
+
+     /* Always need the Result node */
+     pathnode->dummypp = false;
+     pathnode->srfpp = true;
+
+     /*
+      * Estimate number of rows produced by SRFs for each row of input; if
+      * there's more than one in this node, use the maximum.
+      */
+     tlist_rows = 1;
+     foreach(lc, target->exprs)
+     {
+         Node       *node = (Node *) lfirst(lc);
+         double        itemrows;
+
+         itemrows = expression_returns_set_rows(node);
+         if (tlist_rows < itemrows)
+             tlist_rows = itemrows;
+     }
+
+     /*
+      * In addition to the cost of evaluating the tlist, charge cpu_tuple_cost
+      * per input row, and half of cpu_tuple_cost for each added output row.
+      * This is slightly bizarre maybe, but it's what 9.6 did; we may revisit
+      * this estimate later.
+      */
+     pathnode->path.rows = subpath->rows * tlist_rows;
+     pathnode->path.startup_cost = subpath->startup_cost +
+         target->cost.startup;
+     pathnode->path.total_cost = subpath->total_cost +
+         target->cost.startup +
+         (cpu_tuple_cost + target->cost.per_tuple) * subpath->rows +
+         (pathnode->path.rows - subpath->rows) * cpu_tuple_cost / 2;
+
+     return pathnode;
+ }
+
+ /*
   * create_sort_path
   *      Creates a pathnode that represents performing an explicit sort.
   *
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 68096b3..ede7bb9 100644
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
***************
*** 16,24 ****
--- 16,35 ----

  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
+ #include "optimizer/cost.h"
  #include "optimizer/tlist.h"


+ typedef struct
+ {
+     List       *nextlevel_tlist;
+     bool        nextlevel_contains_srfs;
+ } split_pathtarget_context;
+
+ static bool split_pathtarget_walker(Node *node,
+                         split_pathtarget_context *context);
+
+
  /*****************************************************************************
   *        Target list creation and searching utilities
   *****************************************************************************/
*************** apply_pathtarget_labeling_to_tlist(List
*** 759,761 ****
--- 770,960 ----
          i++;
      }
  }
+
+ /*
+  * split_pathtarget_at_srfs
+  *        Split given PathTarget into multiple levels to position SRFs safely
+  *
+  * The executor can only handle set-returning functions that appear at the
+  * top level of the targetlist of a Result plan node.  If we have any SRFs
+  * that are not at top level, we need to split up the evaluation into multiple
+  * plan levels in which each level satisfies this constraint.  This function
+  * creates appropriate PathTarget(s) for each level.
+  *
+  * As an example, consider the tlist expression
+  *        x + srf1(srf2(y + z))
+  * This expression should appear as-is in the top PathTarget, but below that
+  * we must have a PathTarget containing
+  *        x, srf1(srf2(y + z))
+  * and below that, another PathTarget containing
+  *        x, srf2(y + z)
+  * and below that, another PathTarget containing
+  *        x, y, z
+  * When these tlists are processed by setrefs.c, subexpressions that match
+  * output expressions of the next lower tlist will be replaced by Vars,
+  * so that what the executor gets are tlists looking like
+  *        Var1 + Var2
+  *        Var1, srf1(Var2)
+  *        Var1, srf2(Var2 + Var3)
+  *        x, y, z
+  * which satisfy the desired property.
+  *
+  * In some cases, a SRF has already been evaluated in some previous plan level
+  * and we shouldn't expand it again (that is, what we see in the target is
+  * already meant as a reference to a lower subexpression).  So, don't expand
+  * any tlist expressions that appear in input_target, if that's not NULL.
+  * In principle we might need to consider matching subexpressions to
+  * input_target, but for now it's not necessary because only ORDER BY and
+  * GROUP BY expressions are at issue and those will look the same at both
+  * plan levels.
+  *
+  * The outputs of this function are two parallel lists, one a list of
+  * PathTargets and the other an integer list of bool flags indicating
+  * whether the corresponding PathTarget contains any top-level SRFs.
+  * The lists are given in the order they'd need to be evaluated in, with
+  * the "lowest" PathTarget first.  So the last list entry is always the
+  * originally given PathTarget, and any entries before it indicate evaluation
+  * levels that must be inserted below it.  The first list entry must not
+  * contain any SRFs, since it will typically be attached to a plan node
+  * that cannot evaluate SRFs.
+  *
+  * Note: using a list for the flags may seem like overkill, since there
+  * are only a few possible patterns for which levels contain SRFs.
+  * But this representation decouples callers from that knowledge.
+  */
+ void
+ split_pathtarget_at_srfs(PlannerInfo *root,
+                          PathTarget *target, PathTarget *input_target,
+                          List **targets, List **targets_contain_srfs)
+ {
+     /* Initialize output lists to empty; we prepend to them within loop */
+     *targets = *targets_contain_srfs = NIL;
+
+     /* Loop to consider each level of PathTarget we need */
+     for (;;)
+     {
+         bool        target_contains_srfs = false;
+         split_pathtarget_context context;
+         ListCell   *lc;
+
+         context.nextlevel_tlist = NIL;
+         context.nextlevel_contains_srfs = false;
+
+         /*
+          * Scan the PathTarget looking for SRFs.  Top-level SRFs are handled
+          * in this loop, ones lower down are found by split_pathtarget_walker.
+          */
+         foreach(lc, target->exprs)
+         {
+             Node       *node = (Node *) lfirst(lc);
+
+             /*
+              * A tlist item that is just a reference to an expression already
+              * computed in input_target need not be evaluated here, so just
+              * make sure it's included in the next PathTarget.
+              */
+             if (input_target && list_member(input_target->exprs, node))
+             {
+                 context.nextlevel_tlist = lappend(context.nextlevel_tlist, node);
+                 continue;
+             }
+
+             /* Else, we need to compute this expression. */
+             if (IsA(node, FuncExpr) &&
+                 ((FuncExpr *) node)->funcretset)
+             {
+                 /* Top-level SRF: it can be evaluated here */
+                 target_contains_srfs = true;
+                 /* Recursively examine SRF's inputs */
+                 split_pathtarget_walker((Node *) ((FuncExpr *) node)->args,
+                                         &context);
+             }
+             else if (IsA(node, OpExpr) &&
+                      ((OpExpr *) node)->opretset)
+             {
+                 /* Same as above, but for set-returning operator */
+                 target_contains_srfs = true;
+                 split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
+                                         &context);
+             }
+             else
+             {
+                 /* Not a top-level SRF, so recursively examine expression */
+                 split_pathtarget_walker(node, &context);
+             }
+         }
+
+         /*
+          * Prepend current target and associated flag to output lists.
+          */
+         *targets = lcons(target, *targets);
+         *targets_contain_srfs = lcons_int(target_contains_srfs,
+                                           *targets_contain_srfs);
+
+         /*
+          * Done if we found no SRFs anywhere in this target; the tentative
+          * tlist we built for the next level can be discarded.
+          */
+         if (!target_contains_srfs && !context.nextlevel_contains_srfs)
+             break;
+
+         /*
+          * Else build the next PathTarget down, and loop back to process it.
+          * Copy the subexpressions to make sure PathTargets don't share
+          * substructure (might be unnecessary, but be safe); and drop any
+          * duplicate entries in the sub-targetlist.
+          */
+         target = create_empty_pathtarget();
+         add_new_columns_to_pathtarget(target,
+                                (List *) copyObject(context.nextlevel_tlist));
+         set_pathtarget_cost_width(root, target);
+     }
+ }
+
+ /* Recursively examine expressions for split_pathtarget_at_srfs */
+ static bool
+ split_pathtarget_walker(Node *node, split_pathtarget_context *context)
+ {
+     if (node == NULL)
+         return false;
+     if (IsA(node, Var) ||
+         IsA(node, PlaceHolderVar) ||
+         IsA(node, Aggref) ||
+         IsA(node, GroupingFunc) ||
+         IsA(node, WindowFunc))
+     {
+         /*
+          * Pass these items down to the child plan level for evaluation.
+          *
+          * We assume that these constructs cannot contain any SRFs (if one
+          * does, there will be an executor failure from a misplaced SRF).
+          */
+         context->nextlevel_tlist = lappend(context->nextlevel_tlist, node);
+
+         /* Having done that, we need not examine their sub-structure */
+         return false;
+     }
+     else if ((IsA(node, FuncExpr) &&
+               ((FuncExpr *) node)->funcretset) ||
+              (IsA(node, OpExpr) &&
+               ((OpExpr *) node)->opretset))
+     {
+         /*
+          * Pass SRFs down to the child plan level for evaluation, and mark
+          * that it contains SRFs.  (We are not at top level of our own tlist,
+          * else this would have been picked up by split_pathtarget_at_srfs.)
+          */
+         context->nextlevel_tlist = lappend(context->nextlevel_tlist, node);
+         context->nextlevel_contains_srfs = true;
+
+         /* Inputs to the SRF need not be considered here, so we're done */
+         return false;
+     }
+
+     /*
+      * Otherwise, the node is evaluatable within the current PathTarget, so
+      * recurse to examine its inputs.
+      */
+     return expression_tree_walker(node, split_pathtarget_walker,
+                                   (void *) context);
+ }
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2709cc7..0cb42b7 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct ProjectionPath
*** 1293,1298 ****
--- 1293,1299 ----
      Path        path;
      Path       *subpath;        /* path representing input source */
      bool        dummypp;        /* true if no separate Result is needed */
+     bool        srfpp;            /* true if SRFs are being evaluated here */
  } ProjectionPath;

  /*
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 9abef37..1d0fa30 100644
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
*************** extern bool contain_window_function(Node
*** 54,60 ****
  extern WindowFuncLists *find_window_functions(Node *clause, Index maxWinRef);

  extern double expression_returns_set_rows(Node *clause);
- extern double tlist_returns_set_rows(List *tlist);

  extern bool contain_subplans(Node *clause);

--- 54,59 ----
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 71d9154..c452927 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern Path *apply_projection_to_path(Pl
*** 144,149 ****
--- 144,153 ----
                           RelOptInfo *rel,
                           Path *path,
                           PathTarget *target);
+ extern ProjectionPath *create_srf_projection_path(PlannerInfo *root,
+                            RelOptInfo *rel,
+                            Path *subpath,
+                            PathTarget *target);
  extern SortPath *create_sort_path(PlannerInfo *root,
                   RelOptInfo *rel,
                   Path *subpath,
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 0d745a0..edd1e80 100644
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void add_column_to_pathtarget(Pat
*** 61,66 ****
--- 61,69 ----
  extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
  extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
  extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
+ extern void split_pathtarget_at_srfs(PlannerInfo *root,
+                          PathTarget *target, PathTarget *input_target,
+                          List **targets, List **targets_contain_srfs);

  /* Convenience macro to get a PathTarget with valid cost/width fields */
  #define create_pathtarget(root, tlist) \
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 45208a6..e3804e9 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** explain (costs off)
*** 823,829 ****
             ->  Index Only Scan Backward using tenk1_unique2 on tenk1
                   Index Cond: (unique2 IS NOT NULL)
     ->  Result
! (7 rows)

  select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
   max  | g
--- 823,830 ----
             ->  Index Only Scan Backward using tenk1_unique2 on tenk1
                   Index Cond: (unique2 IS NOT NULL)
     ->  Result
!          ->  Result
! (8 rows)

  select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
   max  | g
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 9c3eecf..a7ded3a 100644
*** a/src/test/regress/expected/limit.out
--- b/src/test/regress/expected/limit.out
*************** select currval('testseq');
*** 208,220 ****
  explain (verbose, costs off)
  select unique1, unique2, generate_series(1,10)
    from tenk1 order by unique2 limit 7;
!                         QUERY PLAN
! ----------------------------------------------------------
   Limit
     Output: unique1, unique2, (generate_series(1, 10))
!    ->  Index Scan using tenk1_unique2 on public.tenk1
           Output: unique1, unique2, generate_series(1, 10)
! (4 rows)

  select unique1, unique2, generate_series(1,10)
    from tenk1 order by unique2 limit 7;
--- 208,222 ----
  explain (verbose, costs off)
  select unique1, unique2, generate_series(1,10)
    from tenk1 order by unique2 limit 7;
!                                                                          QUERY PLAN
                                       
!
-------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: unique1, unique2, (generate_series(1, 10))
!    ->  Result
           Output: unique1, unique2, generate_series(1, 10)
!          ->  Index Scan using tenk1_unique2 on public.tenk1
!                Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous,
odd,even, stringu1, stringu2, string4 
! (6 rows)

  select unique1, unique2, generate_series(1,10)
    from tenk1 order by unique2 limit 7;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index f06cfa4..9634fa1 100644
*** a/src/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
*************** SELECT *,
*** 1995,2006 ****
          END)
  FROM
    (VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str);
!  id |       str        |      lower
! ----+------------------+------------------
!   1 |                  |
!   2 | 0000000049404    | 49404
!   3 | FROM 10000000876 | from 10000000876
! (3 rows)

  -- check whole-row-Var handling in nested lateral functions (bug #11703)
  create function extractq2(t int8_tbl) returns int8 as $$
--- 1995,2004 ----
          END)
  FROM
    (VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str);
!  id |      str      | lower
! ----+---------------+-------
!   2 | 0000000049404 | 49404
! (1 row)

  -- check whole-row-Var handling in nested lateral functions (bug #11703)
  create function extractq2(t int8_tbl) returns int8 as $$
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 0fc93d9..e76cb6b 100644
*** a/src/test/regress/expected/subselect.out
--- b/src/test/regress/expected/subselect.out
*************** select * from int4_tbl where
*** 807,830 ****
  explain (verbose, costs off)
  select * from int4_tbl o where (f1, f1) in
    (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
!                            QUERY PLAN
! ----------------------------------------------------------------
!  Hash Semi Join
     Output: o.f1
!    Hash Cond: (o.f1 = "ANY_subquery".f1)
     ->  Seq Scan on public.int4_tbl o
           Output: o.f1
!    ->  Hash
           Output: "ANY_subquery".f1, "ANY_subquery".g
           ->  Subquery Scan on "ANY_subquery"
                 Output: "ANY_subquery".f1, "ANY_subquery".g
                 Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
!                ->  HashAggregate
!                      Output: i.f1, (generate_series(1, 2) / 10)
!                      Group Key: i.f1
!                      ->  Seq Scan on public.int4_tbl i
!                            Output: i.f1
! (15 rows)

  select * from int4_tbl o where (f1, f1) in
    (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
--- 807,834 ----
  explain (verbose, costs off)
  select * from int4_tbl o where (f1, f1) in
    (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
!                             QUERY PLAN
! -------------------------------------------------------------------
!  Nested Loop Semi Join
     Output: o.f1
!    Join Filter: (o.f1 = "ANY_subquery".f1)
     ->  Seq Scan on public.int4_tbl o
           Output: o.f1
!    ->  Materialize
           Output: "ANY_subquery".f1, "ANY_subquery".g
           ->  Subquery Scan on "ANY_subquery"
                 Output: "ANY_subquery".f1, "ANY_subquery".g
                 Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
!                ->  Result
!                      Output: i.f1, ((generate_series(1, 2)) / 10)
!                      ->  Result
!                            Output: i.f1, generate_series(1, 2)
!                            ->  HashAggregate
!                                  Output: i.f1
!                                  Group Key: i.f1
!                                  ->  Seq Scan on public.int4_tbl i
!                                        Output: i.f1
! (19 rows)

  select * from int4_tbl o where (f1, f1) in
    (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index e9bea41..4e87186 100644
*** a/src/test/regress/expected/tsrf.out
--- b/src/test/regress/expected/tsrf.out
*************** SELECT generate_series(1, generate_serie
*** 43,49 ****

  -- srf, with two SRF arguments
  SELECT generate_series(generate_series(1,3), generate_series(2, 4));
! ERROR:  functions and operators can take at most one set argument
  CREATE TABLE few(id int, dataa text, datab text);
  INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar');
  -- SRF output order of sorting is maintained, if SRF is not referenced
--- 43,58 ----

  -- srf, with two SRF arguments
  SELECT generate_series(generate_series(1,3), generate_series(2, 4));
!  generate_series
! -----------------
!                1
!                2
!                2
!                3
!                3
!                4
! (6 rows)
!
  CREATE TABLE few(id int, dataa text, datab text);
  INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar');
  -- SRF output order of sorting is maintained, if SRF is not referenced

Andres Freund <andres@anarazel.de> writes:
> 0003-Avoid-materializing-SRFs-in-the-FROM-list.patch
>   To avoid performance regressions from moving SRFM_ValuePerCall SRFs to
>   ROWS FROM, nodeFunctionscan.c needs to support not materializing
>   output.

I looked over this patch a bit.

>   In my present patch I've *ripped out* the support for materialization
>   in nodeFunctionscan.c entirely. That means that rescans referencing
>   volatile functions can change their behaviour (if a function is
>   rescanned, without having it's parameters changed), and that native
>   backward scan support is gone.  I don't think that's actually an issue.

I think you are wrong on this not being an issue: it is critical that
rescan deliver the same results as before, else for example having a
function RTE on the inside of a nestloop will give nonsensical/broken
results.  I think what we'll have to do is allow the optimization of
skipping the tuplestore only when the function is declared nonvolatile.
(If it is, and it nonetheless gives different results on rescan, it's not
our fault if joins give haywire answers.)  I'm okay with not supporting
backward scan, but wrong answers during rescan is a different animal
entirely.

Moreover, I think we'd all agreed that this effort needs to avoid any
not-absolutely-necessary semantics changes.  This one is not only not
necessary, but it would result in subtle hard-to-detect breakage.

It's conceivable that we could allow the executor to be broken this way
and have the planner fix it by inserting a Material node when joining.
But I think it would be messy and would probably not perform as well as
an internal tuplestore --- for one thing, because the planner can't know
whether the function would return a tuplestore, making the external
materialization redundant.

Another idea is that we could extend the set of ExecInitNode flags
(EXEC_FLAG_REWIND etc) to tell child nodes whether they need to implement
rescan correctly in this sense; if they are not RHS children of nestloops,
and maybe one or two other cases, they don't.  That would give another
route by which nodeFunctionscan could decide that it can skip
materialization in common cases.
        regards, tom lane



On 2016-09-15 15:23:58 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> >   In my present patch I've *ripped out* the support for materialization
> >   in nodeFunctionscan.c entirely. That means that rescans referencing
> >   volatile functions can change their behaviour (if a function is
> >   rescanned, without having it's parameters changed), and that native
> >   backward scan support is gone.  I don't think that's actually an issue.
> 
> I think you are wrong on this not being an issue: it is critical that
> rescan deliver the same results as before, else for example having a
> function RTE on the inside of a nestloop will give nonsensical/broken
> results.

I find that quite unconvincing. We quite freely re-evaluate functions in
the targetlist again, even if they're volatile and/or SRFs.

If we implement tSRFs as pipeline nodes, we can "simply" default to the
never materializing behaviour there I guess.


> Moreover, I think we'd all agreed that this effort needs to avoid any
> not-absolutely-necessary semantics changes.

I don't agree with that. Adding pointless complications for a niche
edge cases of niche features isn't worth it.


> Another idea is that we could extend the set of ExecInitNode flags
> (EXEC_FLAG_REWIND etc) to tell child nodes whether they need to implement
> rescan correctly in this sense; if they are not RHS children of nestloops,
> and maybe one or two other cases, they don't.  That would give another
> route by which nodeFunctionscan could decide that it can skip
> materialization in common cases.

That's something I've wondered about too. Materializing if rescans are
required is quite acceptable, and probably rather rare in
practice. Seems not unlikely that that information would be valuable for
other node types too.


Regards,

Andres



Hi,

On 2016-09-14 19:28:25 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 19:35:22 -0400, Tom Lane wrote:
> >> Anyway I'll draft a prototype and then we can compare.
> 
> > Ok, cool.
> 
> Here's a draft patch that is just meant to investigate what the planner
> changes might look like if we do it in the pipelined-result way.

Nice.


> A difficulty with this restriction is that if you have a query like
> "select f1, generate_series(1,2) / 10 from tab" then you end up with both
> a SRF-executing Result and a separate scalar-projection Result above it,
> because the division-by-ten has to happen in a separate plan level.

Makes sense.  I guess we could teach the SRF pipeline node to execute a
series of such steps. Hm. That makes me think of something:

Hm. One thing I wonder about with this approach, is how we're going to
handle something absurd like:
SELECT generate_series(1, generate_series(1, 2)), generate_series(1, generate_series(2,4));

I guess we have to do here is
Step: generate_series(1,2), 1, 2, 4
Step: generate_series(1, Var(generate_series(1,2))), 1, 2, 4
Step: Var(generate_series(1, Var(generate_series(1,2)))), 1, generate_series(2, 4)
Step: Var(generate_series(1, Var(generate_series(1,2)))), generate_series(1, Var(generate_series(2, 4)))

But that'd still not have the same lockstepping behaviour, right?  I'm
at a conference, and half-ill, so I might just standing on my own brain
here.


> The planner's notions about the cost of Result make it think that this is
> quite expensive --- mainly because the upper Result will be iterated once
> per SRF output row, so that you get hit with cpu_tuple_cost per output row.
> And that in turn leads it to change plans in one or two cases in the
> regression tests.  Maybe that's fine.  I'm worried though that it's right
> that this will be unduly expensive.  So I'm kind of tempted to define the
> SRF-executing node as acting more like, say, Agg or WindowFunc, in that
> it has a list of SRFs to execute and then it has the ability to project a
> scalar tlist on top of those results.

Hah, was thinking the same above ;)


> On the whole I'm pretty pleased with this approach, at least from the
> point of view of the planner.  The net addition of planner code is
> smaller than what you had,

Not by much. But I do agree that there's some advantages here.


> and though I'm no doubt biased, I think this
> version is much cleaner.

Certainly seems a bit easier to extend and adjust behaviour. Not having
to deal with enforcing join order, and having less issues with
determining what to push where is certainly advantageous.  After all,
that was why I initially was thinking of tis approach.

> Also, though this patch doesn't address exactly
> how we might do it, it's fairly clear that it'd be possible to allow
> FDWs and CustomScans to implement SRF execution, eg pushing a SRF down to
> a foreign server, in a reasonably straightforward extension of the
> existing upper-pathification hooks.  If we go with the lateral function
> RTE approach, that's going to be somewhere between hard and impossible.

Hm. Not sure if there's that much point in doing that, but I agree that
the LATERAL approach adds more restrictions.


> So I think we should continue investigating this way of doing things.
> I'll try to take a look at the executor end of it tomorrow.  However
> I'm leaving Friday for a week's vacation, and may not have anything to
> show before that.

If you have something that's halfway recognizable, could you perhaps
post it?

Regards,

Andres



Andres Freund <andres@anarazel.de> writes:
> Hm. One thing I wonder about with this approach, is how we're going to
> handle something absurd like:
> SELECT generate_series(1, generate_series(1, 2)), generate_series(1, generate_series(2,4));

The patch that I posted would run both the generate_series(1, 2) and
generate_series(2,4) calls in the same SRF node, forcing them to run in
lockstep, after which their results would be fed to the SRF node doing
the top-level SRFs.  We could probably change it to run them in separate
nodes, but I don't see any principled way to decide which one goes first
(and in some variants of this example, it would matter).  I think the
LATERAL approach would face exactly the same issues: how many LATERAL
nodes do you use, and what's their join order?

I think we could get away with defining it like this (ie, SRFs at the same
SRF nesting level run in lockstep) as long as it's documented.  Whatever
the current behavior is for such cases would be pretty bizarre too.
        regards, tom lane



On 2016-09-15 16:48:59 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > Hm. One thing I wonder about with this approach, is how we're going to
> > handle something absurd like:
> > SELECT generate_series(1, generate_series(1, 2)), generate_series(1, generate_series(2,4));
> 
> The patch that I posted would run both the generate_series(1, 2) and
> generate_series(2,4) calls in the same SRF node, forcing them to run in
> lockstep, after which their results would be fed to the SRF node doing
> the top-level SRFs.  We could probably change it to run them in separate
> nodes, but I don't see any principled way to decide which one goes first
> (and in some variants of this example, it would matter).

I think that's fine. I personally still think we're *much* better off
getting rid of the non-lockstep variants. You're still on the fence
about retaining the LCM behaviour (for the same nesting level at least)?


> I think the LATERAL approach would face exactly the same issues: how
> many LATERAL nodes do you use, and what's their join order?

I think this specific issue could be handled in a bit easier to grasp
variant. My PoC basically generated one RTE for each "query
level". There'd have been one RTE for generate_series(1,2), one for
gs(2,4) and one for gs(1, var(gs(1,2))), gs(1, var(gs(2,4))). Lateral
machiner would force the join order to have the argument srfs first, and
then the twoi combined srf with lateral arguments after that.

> I think we could get away with defining it like this (ie, SRFs at the same
> SRF nesting level run in lockstep) as long as it's documented.  Whatever
> the current behavior is for such cases would be pretty bizarre too.

Indeed.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-15 16:48:59 -0400, Tom Lane wrote:
>> The patch that I posted would run both the generate_series(1, 2) and
>> generate_series(2,4) calls in the same SRF node, forcing them to run in
>> lockstep, after which their results would be fed to the SRF node doing
>> the top-level SRFs.  We could probably change it to run them in separate
>> nodes, but I don't see any principled way to decide which one goes first
>> (and in some variants of this example, it would matter).

> I think that's fine. I personally still think we're *much* better off
> getting rid of the non-lockstep variants. You're still on the fence
> about retaining the LCM behaviour (for the same nesting level at least)?

I'm happy to get rid of the LCM behavior, I just want to have some wiggle
room to be able to get it back if somebody really needs it.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Michael Paquier
Date:
On Wed, Aug 24, 2016 at 3:55 AM, Andres Freund <andres@anarazel.de> wrote:
> Comments?

This thread has no activity for some time now and it is linked to this CF entry:
https://commitfest.postgresql.org/10/759/
I am marking it as returned with feedback..
-- 
Michael



On Fri, Sep 16, 2016 at 6:12 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I'm happy to get rid of the LCM behavior, I just want to have some wiggle
> room to be able to get it back if somebody really needs it.

Er, actually no that's this thread for this CF entry:
https://commitfest.postgresql.org/10/759/
Still there has not been much activity.
-- 
Michael



Hi Tom,

On 2016-09-14 19:28:25 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2016-09-12 19:35:22 -0400, Tom Lane wrote:
> >> Anyway I'll draft a prototype and then we can compare.
> 
> > Ok, cool.
> 
> Here's a draft patch that is just meant to investigate what the planner
> changes might look like if we do it in the pipelined-result way.
> Accordingly, I didn't touch the executor, but just had it emit regular
> Result nodes for SRF-execution steps.  However, the SRFs are all
> guaranteed to appear at top level of their respective tlists, so that
> those Results could be replaced with something that works like
> nodeFunctionscan.

> So I think we should continue investigating this way of doing things.
> I'll try to take a look at the executor end of it tomorrow.  However
> I'm leaving Friday for a week's vacation, and may not have anything to
> show before that.

Are you planning to work on the execution side of things? I otherwise
can take a stab...

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> On 2016-09-14 19:28:25 -0400, Tom Lane wrote:
>> So I think we should continue investigating this way of doing things.
>> I'll try to take a look at the executor end of it tomorrow.  However
>> I'm leaving Friday for a week's vacation, and may not have anything to
>> show before that.

> Are you planning to work on the execution side of things? I otherwise
> can take a stab...

My plan is to start on this when I go back into commitfest mode,
but right now I'm trying to produce a draft patch for RLS changes.
        regards, tom lane



Re: Changed SRF in targetlist handling

From
Robert Haas
Date:
On Mon, Aug 22, 2016 at 4:20 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> On 2016-08-17 17:41:28 -0700, Andres Freund wrote:
>>> Tom, do you think this is roughly going in the right direction?
>
> I've not had time to look at this patch, I'm afraid.  If you still
> want me to, I can make time in a day or so.

Tom, it's been about 3.5 months since you wrote this.  I think it
would be really valuable if you could get to this RSN because the
large patch set posted on the "Changed SRF in targetlist handling"
thread is backed up behind this -- and I think that's really valuable
work which I don't want to see slip out of this release.  At the same
time, both that and this are quite invasive, and I don't want it all
to get committed the day before feature freeze, because that will mess
up the schedule.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Changed SRF in targetlist handling

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> Tom, it's been about 3.5 months since you wrote this.  I think it
> would be really valuable if you could get to this RSN because the
> large patch set posted on the "Changed SRF in targetlist handling"
> thread is backed up behind this -- and I think that's really valuable
> work which I don't want to see slip out of this release.

Yeah, I was busy with other stuff during the recent commitfest.
I'll try to get back to this.  There's still only 24 hours in a day,
though.  (And no, [1] is not enough to help.)
        regards, tom lane

[1] https://www.theguardian.com/science/2016/dec/07/earths-day-lengthens-by-two-milliseconds-a-century-astronomers-find



Re: [HACKERS] Changed SRF in targetlist handling

From
Robert Haas
Date:
On Mon, Aug 22, 2016 at 4:20 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> On 2016-08-17 17:41:28 -0700, Andres Freund wrote:
>>> Tom, do you think this is roughly going in the right direction?
>
> I've not had time to look at this patch, I'm afraid.  If you still
> want me to, I can make time in a day or so.

Tom, it's been about 3.5 months since you wrote this.  I think it
would be really valuable if you could get to this RSN because the
large patch set posted on the "Changed SRF in targetlist handling"
thread is backed up behind this -- and I think that's really valuable
work which I don't want to see slip out of this release.  At the same
time, both that and this are quite invasive, and I don't want it all
to get committed the day before feature freeze, because that will mess
up the schedule.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: [HACKERS] Changed SRF in targetlist handling

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> Tom, it's been about 3.5 months since you wrote this.  I think it
> would be really valuable if you could get to this RSN because the
> large patch set posted on the "Changed SRF in targetlist handling"
> thread is backed up behind this -- and I think that's really valuable
> work which I don't want to see slip out of this release.

Yeah, I was busy with other stuff during the recent commitfest.
I'll try to get back to this.  There's still only 24 hours in a day,
though.  (And no, [1] is not enough to help.)
        regards, tom lane

[1] https://www.theguardian.com/science/2016/dec/07/earths-day-lengthens-by-two-milliseconds-a-century-astronomers-find



On 2016-10-31 09:06:39 -0700, Andres Freund wrote:
> On 2016-09-14 19:28:25 -0400, Tom Lane wrote:
> > Andres Freund <andres@anarazel.de> writes:
> > > On 2016-09-12 19:35:22 -0400, Tom Lane wrote:
> > Here's a draft patch that is just meant to investigate what the planner
> > changes might look like if we do it in the pipelined-result way.
> > Accordingly, I didn't touch the executor, but just had it emit regular
> > Result nodes for SRF-execution steps.  However, the SRFs are all
> > guaranteed to appear at top level of their respective tlists, so that
> > those Results could be replaced with something that works like
> > nodeFunctionscan.
> 
> > So I think we should continue investigating this way of doing things.
> > I'll try to take a look at the executor end of it tomorrow.  However
> > I'm leaving Friday for a week's vacation, and may not have anything to
> > show before that.
> 
> Are you planning to work on the execution side of things? I otherwise
> can take a stab...

Doing so now.

Andres



On 2017-01-15 19:29:52 -0800, Andres Freund wrote:
> On 2016-10-31 09:06:39 -0700, Andres Freund wrote:
> > On 2016-09-14 19:28:25 -0400, Tom Lane wrote:
> > > Andres Freund <andres@anarazel.de> writes:
> > > > On 2016-09-12 19:35:22 -0400, Tom Lane wrote:
> > > Here's a draft patch that is just meant to investigate what the planner
> > > changes might look like if we do it in the pipelined-result way.
> > > Accordingly, I didn't touch the executor, but just had it emit regular
> > > Result nodes for SRF-execution steps.  However, the SRFs are all
> > > guaranteed to appear at top level of their respective tlists, so that
> > > those Results could be replaced with something that works like
> > > nodeFunctionscan.
> >
> > > So I think we should continue investigating this way of doing things.
> > > I'll try to take a look at the executor end of it tomorrow.  However
> > > I'm leaving Friday for a week's vacation, and may not have anything to
> > > show before that.
> >
> > Are you planning to work on the execution side of things? I otherwise
> > can take a stab...
>
> Doing so now.

That worked quite well.  So we have a few questions, before I clean this
up:

- For now the node is named 'Srf' both internally and in explain - not sure if we want to make that something
longer/easierto understand for others? Proposals? TargetFunctionScan? SetResult?
 

- We could alternatively add all this into the Result node - it's not actually a lot of new code, and most of that is
boilerplatestuff about adding a new node.  I'm ok with both.
 

- I continued with the division of Labor that Tom had set up, so we're creating one Srf node for each "nested" set of
SRFs. We'd discussed nearby to change that for one node/path for all nested SRFs, partially because of costing.  But I
don'tlike the idea that much anymore. The implementation seems cleaner (and probably faster) this way, and I don't
thinknested targetlist SRFs are something worth optimizing for.  Anybody wants to argue differently?
 

- I chose to error out if a retset function appears in ExecEvalFunc/Oper and make both unconditionally set evalfunc to
ExecMakeFunctionResultNoSets. ExecMakeFunctionResult() is now externally visible.  That seems like the least noisy way
tochange things over, but I'm open for different proposals.
 

Comments?

Regards,

Andres



Andres Freund wrote:

> That worked quite well.  So we have a few questions, before I clean this
> up:
> 
> - For now the node is named 'Srf' both internally and in explain - not
>   sure if we want to make that something longer/easier to understand for
>   others? Proposals? TargetFunctionScan? SetResult?
> 
> - We could alternatively add all this into the Result node - it's not
>   actually a lot of new code, and most of that is boilerplate stuff
>   about adding a new node.  I'm ok with both.

Hmm.  I wonder if your stuff could be used as support code for
XMLTABLE[1].  Currently it has a bit of additional code of its own,
though admittedly it's very little code executor-side.  Would you mind
sharing a patch, or more details on how it works?

[1] https://www.postgresql.org/message-id/CAFj8pRA_KEukOBXvS4V-imoEEsXu0pD0AsHV0-MwRFDRWte8Lg@mail.gmail.com

> - I continued with the division of Labor that Tom had set up, so we're
>   creating one Srf node for each "nested" set of SRFs.  We'd discussed
>   nearby to change that for one node/path for all nested SRFs, partially
>   because of costing.  But I don't like the idea that much anymore. The
>   implementation seems cleaner (and probably faster) this way, and I
>   don't think nested targetlist SRFs are something worth optimizing
>   for.  Anybody wants to argue differently?

Nested targetlist SRFs make my head spin.  I suppose they may have some
use, but where would you really want this:

alvherre=# select generate_series(1, generate_series(2, 4));generate_series 
─────────────────              1              2              1              2              3              1
2              3              4
 
(9 filas)

instead of the much more sensible

alvherre=# select i, j from generate_series(2, 4) i, generate_series(1, i) j;i │ j 
───┼───2 │ 12 │ 23 │ 13 │ 23 │ 34 │ 14 │ 24 │ 34 │ 4
(9 filas)

?  If supporting the former makes it harder to support/optimize more
reasonable cases, it seems fair game to leave them behind.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



On 2017-01-16 12:17:46 -0300, Alvaro Herrera wrote:
> Andres Freund wrote:
> 
> > That worked quite well.  So we have a few questions, before I clean this
> > up:
> > 
> > - For now the node is named 'Srf' both internally and in explain - not
> >   sure if we want to make that something longer/easier to understand for
> >   others? Proposals? TargetFunctionScan? SetResult?
> > 
> > - We could alternatively add all this into the Result node - it's not
> >   actually a lot of new code, and most of that is boilerplate stuff
> >   about adding a new node.  I'm ok with both.
> 
> Hmm.  I wonder if your stuff could be used as support code for
> XMLTABLE[1].

I don't immediately see what functionality overlaps, could you expand on
that?


> Currently it has a bit of additional code of its own,
> though admittedly it's very little code executor-side.  Would you mind
> sharing a patch, or more details on how it works?

Can do both; cleaning up the patch now. What we're talking about here is
a way to implement targetlist SRF that is based on:

1) a patch by Tom that creates additional Result (or now Srf) executor  nodes containing SRF evaluation. This
guaranteesthat only Result/Srf  nodes have to deal with targetlist SRF evaluation.
 

2) new code to evaluate SRFs in the new Result/Srf node, that doesn't  rely on ExecEvalExpr et al. to have a IsDone
argument.Instead  there's special code to handle that in the new node. That's possible  because it's now guaranted that
allSRFs are "toplevel" in the  relevant targetlist(s).
 

3) Removal of all nearly tSRF related code execQual.c and other  executor/ files, including the
node->ps.ps_TupFromTlistchecks  everywhere.
 

makes sense?


> > - I continued with the division of Labor that Tom had set up, so we're
> >   creating one Srf node for each "nested" set of SRFs.  We'd discussed
> >   nearby to change that for one node/path for all nested SRFs, partially
> >   because of costing.  But I don't like the idea that much anymore. The
> >   implementation seems cleaner (and probably faster) this way, and I
> >   don't think nested targetlist SRFs are something worth optimizing
> >   for.  Anybody wants to argue differently?
> 
> Nested targetlist SRFs make my head spin.  I suppose they may have some
> use, but where would you really want this:

I think there's some cases where it can be useful. Targetlist SRFs as a
whole really are much more about backward compatibility than anything :)


> ?  If supporting the former makes it harder to support/optimize more
> reasonable cases, it seems fair game to leave them behind.

I don't want to desupport them, just don't want to restructure (one node
doing several levels of SRFs, instead of one per level) just to make it
easier to give good estimates.

Greetings,

Andres Freund



Andres Freund wrote:
> On 2017-01-16 12:17:46 -0300, Alvaro Herrera wrote:
> > Andres Freund wrote:
> > 
> > > That worked quite well.  So we have a few questions, before I clean this
> > > up:
> > > 
> > > - For now the node is named 'Srf' both internally and in explain - not
> > >   sure if we want to make that something longer/easier to understand for
> > >   others? Proposals? TargetFunctionScan? SetResult?
> > > 
> > > - We could alternatively add all this into the Result node - it's not
> > >   actually a lot of new code, and most of that is boilerplate stuff
> > >   about adding a new node.  I'm ok with both.
> > 
> > Hmm.  I wonder if your stuff could be used as support code for
> > XMLTABLE[1].
> 
> I don't immediately see what functionality overlaps, could you expand on
> that?

Well, I haven't read any previous patches in this area, but the xmltable
patch adds a new way of handling set-returning expressions, so it
appears vaguely related.  These aren't properly functions in the current
sense of the word, though.  There is some parallel to what
ExecMakeFunctionResult does, which I suppose is related.

> > Currently it has a bit of additional code of its own,
> > though admittedly it's very little code executor-side.  Would you mind
> > sharing a patch, or more details on how it works?
> 
> Can do both; cleaning up the patch now. What we're talking about here is
> a way to implement targetlist SRF that is based on:
> 
> 1) a patch by Tom that creates additional Result (or now Srf) executor
>    nodes containing SRF evaluation. This guarantees that only Result/Srf
>    nodes have to deal with targetlist SRF evaluation.
> 
> 2) new code to evaluate SRFs in the new Result/Srf node, that doesn't
>    rely on ExecEvalExpr et al. to have a IsDone argument. Instead
>    there's special code to handle that in the new node. That's possible
>    because it's now guaranted that all SRFs are "toplevel" in the
>    relevant targetlist(s).
> 
> 3) Removal of all nearly tSRF related code execQual.c and other
>    executor/ files, including the node->ps.ps_TupFromTlist checks
>    everywhere.
> 
> makes sense?

Hmm, okay.  (The ps_TupFromTlist thing has long seemed an ugly
construction.)  I think the current term for this kind of thing is
TableFunction -- are you really naming this "Srf" literally?  It seems
strange, but maybe it's just me.

> > Nested targetlist SRFs make my head spin.  I suppose they may have some
> > use, but where would you really want this:
> 
> I think there's some cases where it can be useful. Targetlist SRFs as a
> whole really are much more about backward compatibility than anything :)

Sure.

> > ?  If supporting the former makes it harder to support/optimize more
> > reasonable cases, it seems fair game to leave them behind.
> 
> I don't want to desupport them, just don't want to restructure (one node
> doing several levels of SRFs, instead of one per level) just to make it
> easier to give good estimates.

No objections.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Andres Freund <andres@anarazel.de> writes:
> That worked quite well.  So we have a few questions, before I clean this
> up:

> - For now the node is named 'Srf' both internally and in explain - not
>   sure if we want to make that something longer/easier to understand for
>   others? Proposals? TargetFunctionScan? SetResult?

"Srf" is ugly as can be, and unintelligible.  SetResult might be OK.

> - I continued with the division of Labor that Tom had set up, so we're
>   creating one Srf node for each "nested" set of SRFs.  We'd discussed
>   nearby to change that for one node/path for all nested SRFs, partially
>   because of costing.  But I don't like the idea that much anymore. The
>   implementation seems cleaner (and probably faster) this way, and I
>   don't think nested targetlist SRFs are something worth optimizing
>   for.  Anybody wants to argue differently?

Not me.

> Comments?

Hard to comment on your other points without a patch to look at.
        regards, tom lane



Hi,

On 2017-01-16 14:13:18 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > That worked quite well.  So we have a few questions, before I clean this
> > up:
>
> > - For now the node is named 'Srf' both internally and in explain - not
> >   sure if we want to make that something longer/easier to understand for
> >   others? Proposals? TargetFunctionScan? SetResult?
>
> "Srf" is ugly as can be, and unintelligible.  SetResult might be OK.

Named it SetResult - imo looks ok.  I think I do prefer the separate
node type over re-using Result.  The planner integration looks cleaner
to me due to not needing the srfpp special cases and such.


> > Comments?
>
> Hard to comment on your other points without a patch to look at.

Attached the current version. There's a *lot* of pending cleanup needed
(especially in execQual.c) removing outdated code/comments etc, but this
seems good enough for a first review.  I'd want that cleanup done in a
separate patch anyway.


Attached are two patches. The first is just a rebased version (just some
hunk offset changed) of your planner patch, on top of that is my
executor patch.  My patch moves some minor detail in yours around, and I
do think they should eventually be merged; but leaving it split for a
round displays the changes more cleanly.

Additional questions:
- do we care about SRFs that don't actually return a set? If so we need
  to change the error checking code in ExecEvalFunc/Oper and move it to
  the actual invocation.
- the FuncExpr/OpExpr check in ExecMakeFunctionResult is fairly ugly imo
  - but I don't quite see a much better solution.

Greetings,

Andres

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachment
On 2017-01-16 16:04:34 -0300, Alvaro Herrera wrote:
> Andres Freund wrote:
> > On 2017-01-16 12:17:46 -0300, Alvaro Herrera wrote:
> > > Hmm.  I wonder if your stuff could be used as support code for
> > > XMLTABLE[1].
> > 
> > I don't immediately see what functionality overlaps, could you expand on
> > that?
> 
> Well, I haven't read any previous patches in this area, but the xmltable
> patch adds a new way of handling set-returning expressions, so it
> appears vaguely related.

Ugh. That's not good - I'm about to remove isDone. Like completely.
That's why I'm actually working on all this, because random expressions
returning more rows makes optimizing expression evaluation a lot harder.

> These aren't properly functions in the current sense of the word,
> though.

Why aren't they? Looks like it'd be doable to do so, at least below the
parser level?


Regards,

Andres



Andres Freund wrote:
> On 2017-01-16 16:04:34 -0300, Alvaro Herrera wrote:
> > Andres Freund wrote:
> > > On 2017-01-16 12:17:46 -0300, Alvaro Herrera wrote:
> > > > Hmm.  I wonder if your stuff could be used as support code for
> > > > XMLTABLE[1].
> > > 
> > > I don't immediately see what functionality overlaps, could you expand on
> > > that?
> > 
> > Well, I haven't read any previous patches in this area, but the xmltable
> > patch adds a new way of handling set-returning expressions, so it
> > appears vaguely related.
> 
> Ugh. That's not good - I'm about to remove isDone. Like completely.
> That's why I'm actually working on all this, because random expressions
> returning more rows makes optimizing expression evaluation a lot harder.

Argh.

> > These aren't properly functions in the current sense of the word,
> > though.
> 
> Why aren't they? Looks like it'd be doable to do so, at least below the
> parser level?

Hmm ...

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



On 2017-01-16 12:52:14 -0800, Andres Freund wrote:
> > > Comments?
> >
> > Hard to comment on your other points without a patch to look at.
> 
> Attached the current version. There's a *lot* of pending cleanup needed
> (especially in execQual.c) removing outdated code/comments etc, but this
> seems good enough for a first review.  I'd want that cleanup done in a
> separate patch anyway.

Here's a version with a lot of that pending cleanup added (and other
light updates).  Most notably all SRF related code is gone from
executor/ excepting ExecMakeFunctionResultSet and nodeSetResult.  I'm
sure there's minor remaining references somewhere, but that's the
majority.

I think when committing this the first two patches should be combined,
but the later cleanup patch one not.  It hides too many of the actually
relevant changes.

Greetings,

Andres Freund

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachment
On Mon, Jan 16, 2017 at 2:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> That worked quite well.  So we have a few questions, before I clean this
>> up:
>
>> - For now the node is named 'Srf' both internally and in explain - not
>>   sure if we want to make that something longer/easier to understand for
>>   others? Proposals? TargetFunctionScan? SetResult?
>
> "Srf" is ugly as can be, and unintelligible.  SetResult might be OK.

The operation we're performing here, IIUC, is projection.  SetResult
lacks a verb, although Set could be confused with one; someone might
think this is the node that sets a result, whatever that means.
Anyway, I suggest working Project in there somehow.  If Project by
itself seems like it's too generic, perhaps ProjectSet or
ProjectSetResult would be suitable.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Jan 16, 2017 at 2:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> "Srf" is ugly as can be, and unintelligible.  SetResult might be OK.

> The operation we're performing here, IIUC, is projection.  SetResult
> lacks a verb, although Set could be confused with one; someone might
> think this is the node that sets a result, whatever that means.
> Anyway, I suggest working Project in there somehow.  If Project by
> itself seems like it's too generic, perhaps ProjectSet or
> ProjectSetResult would be suitable.

Andres' patch is already using "SetProjectionPath" for the path struct
type.  Maybe make that "ProjectSetPath", giving rise to a "ProjectSet"
plan node?

I'm happy to do a global-search-and-replace while I'm reviewing the
patch, but let's decide on names PDQ.
        regards, tom lane



On Tue, Jan 17, 2017 at 12:52 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Mon, Jan 16, 2017 at 2:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> "Srf" is ugly as can be, and unintelligible.  SetResult might be OK.
>
>> The operation we're performing here, IIUC, is projection.  SetResult
>> lacks a verb, although Set could be confused with one; someone might
>> think this is the node that sets a result, whatever that means.
>> Anyway, I suggest working Project in there somehow.  If Project by
>> itself seems like it's too generic, perhaps ProjectSet or
>> ProjectSetResult would be suitable.
>
> Andres' patch is already using "SetProjectionPath" for the path struct
> type.  Maybe make that "ProjectSetPath", giving rise to a "ProjectSet"
> plan node?

+1.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Hi,

On 2017-01-17 12:52:20 -0500, Tom Lane wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
> > On Mon, Jan 16, 2017 at 2:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> "Srf" is ugly as can be, and unintelligible.  SetResult might be OK.
>
> > The operation we're performing here, IIUC, is projection.  SetResult
> > lacks a verb, although Set could be confused with one; someone might
> > think this is the node that sets a result, whatever that means.
> > Anyway, I suggest working Project in there somehow.  If Project by
> > itself seems like it's too generic, perhaps ProjectSet or
> > ProjectSetResult would be suitable.

I'd not have gone for SetResult if we didn't already have Result.  I'm
not super happy ending up having Project in ProjectSet but not in the
Result that end up doing the majority of the projection.  But eh, we can
live with it.


> Andres' patch is already using "SetProjectionPath" for the path struct
> type.  Maybe make that "ProjectSetPath", giving rise to a "ProjectSet"
> plan node?

WFM.


> I'm happy to do a global-search-and-replace while I'm reviewing the
> patch, but let's decide on names PDQ.

Yes, let's decide soon please.


Greeting,

Andres



Andres Freund <andres@anarazel.de> writes:
> I'd not have gone for SetResult if we didn't already have Result.  I'm
> not super happy ending up having Project in ProjectSet but not in the
> Result that end up doing the majority of the projection.  But eh, we can
> live with it.

Using Result for two completely different things is a wart though.  If we
had it to do over I think we'd define Result as a scan node that produces
rows from no input, and create a separate Project node for the case of
projecting from input tuples.  People are used to seeing Result in EXPLAIN
output, so it's not worth the trouble of changing that IMO, but we don't
have to use it as a model for more node types.
        regards, tom lane



On Tue, Jan 17, 2017 at 1:18 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andres Freund <andres@anarazel.de> writes:
>> I'd not have gone for SetResult if we didn't already have Result.  I'm
>> not super happy ending up having Project in ProjectSet but not in the
>> Result that end up doing the majority of the projection.  But eh, we can
>> live with it.
>
> Using Result for two completely different things is a wart though.  If we
> had it to do over I think we'd define Result as a scan node that produces
> rows from no input, and create a separate Project node for the case of
> projecting from input tuples.  People are used to seeing Result in EXPLAIN
> output, so it's not worth the trouble of changing that IMO, but we don't
> have to use it as a model for more node types.

+1, although I think changing the existing node would be fine too if
somebody wanted to do the work.  It's not worth having that wart
forever just to avoid whatever minor pain-of-adjustment might be
involved.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Robert Haas <robertmhaas@gmail.com> writes:
> On Tue, Jan 17, 2017 at 1:18 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Using Result for two completely different things is a wart though.  If we
>> had it to do over I think we'd define Result as a scan node that produces
>> rows from no input, and create a separate Project node for the case of
>> projecting from input tuples.  People are used to seeing Result in EXPLAIN
>> output, so it's not worth the trouble of changing that IMO, but we don't
>> have to use it as a model for more node types.

> +1, although I think changing the existing node would be fine too if
> somebody wanted to do the work.  It's not worth having that wart
> forever just to avoid whatever minor pain-of-adjustment might be
> involved.

Although ... looking closer at Andres' patch, the new node type *is*
channeling Result, in the sense that it might or might not have any input
plan.  This probably traces to what I wrote in September:

+     * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath,
+     * don't bother with it, just make a Result with no input.  This avoids an
+     * extra Result plan node when doing "SELECT srf()".  Depending on what we
+     * decide about the desired plan structure for SRF-expanding nodes, this
+     * optimization might have to go away, and in any case it'll probably look
+     * a good bit different.

I'm not convinced that that optimization is worth preserving, but if we
keep it then ProjectSet isn't le mot juste here, any more than you'd want
to rename Result to Project without changing its existing functionality.
        regards, tom lane



On 2017-01-17 13:43:38 -0500, Tom Lane wrote:
> Although ... looking closer at Andres' patch, the new node type *is*
> channeling Result, in the sense that it might or might not have any input
> plan.  This probably traces to what I wrote in September:
>
> +     * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath,
> +     * don't bother with it, just make a Result with no input.  This avoids an
> +     * extra Result plan node when doing "SELECT srf()".  Depending on what we
> +     * decide about the desired plan structure for SRF-expanding nodes, this
> +     * optimization might have to go away, and in any case it'll probably look
> +     * a good bit different.
>
> I'm not convinced that that optimization is worth preserving, but if we
> keep it then ProjectSet isn't le mot juste here, any more than you'd want
> to rename Result to Project without changing its existing
> functionality.

Right. I'd removed that, and re-added it; primarily because the plans
looked more complex without it. After all, you'd thought it worth adding
that hack ;)   I'm happy with removing it again too.

Andres



Andres Freund <andres@anarazel.de> writes:
> On 2017-01-17 13:43:38 -0500, Tom Lane wrote:
>> I'm not convinced that that optimization is worth preserving, but if we
>> keep it then ProjectSet isn't le mot juste here, any more than you'd want
>> to rename Result to Project without changing its existing
>> functionality.

> Right. I'd removed that, and re-added it; primarily because the plans
> looked more complex without it. After all, you'd thought it worth adding
> that hack ;)   I'm happy with removing it again too.

Well, it seemed reasonable to do that as long as the only cost was ten or
so lines in create_projection_plan.  But if we're contorting not only the
behavior but the very name of the SRF-evaluation plan node type, that's
not negligible cost anymore.  So now I'm inclined to take it out.
        regards, tom lane



I did a review pass over 0001 and 0002.  I think the attached updated
version is committable ... except for one thing.  The more I look at it,
the more disturbed I am by the behavioral change shown in rangefuncs.out
--- that's the SRF-in-one-arm-of-CASE issue.  (The changes in tsrf.out
are fine and as per agreement.)  We touched lightly on that point far
upthread, but didn't really resolve it.  What's bothering me is that
we're changing, silently, from a reasonably-intuitive behavior to a
completely-not-intuitive one.  Since we got a bug report for the previous
less-than-intuitive behavior for such cases, it's inevitable that we'll
get bug reports for this.  I think it'd be far better to throw error for
SRF-inside-a-CASE.  If we don't, we certainly need to document this,
and I'm not very sure how to explain it clearly.

Upthread we had put COALESCE in the same bucket, but I think that's less
of a problem, because in typical usages the SRF would be in the first
argument and so users wouldn't be expecting conditional evaluation.

Anyway, I've not done anything about that in the attached.  What I did do:

* Merge 0001 and 0002.  I appreciate you having separated that for my
review, but it doesn't make any sense to commit the parts of 0001 that
you undid in 0002.

* Rename the node types as per yesterday's discussion.

* Require Project to always have an input plan node.

* Obviously, ExecMakeFunctionResultSet can be greatly simplified now
that it need not deal with hasSetArg cases.  I saw you'd left that
for later, which is mostly fine, but I did lobotomize it just enough
to throw an error if it gets a set result from an argument.  Without
that, we wouldn't really be testing that the planner splits nested
SRFs correctly.

* This bit in ExecProjectSRF was no good:

+         else if (IsA(gstate->arg, FuncExprState) &&
+                  ((FuncExpr *) gstate->arg->expr)->funcretset)

because FuncExprState is used for more node types than just FuncExpr;
in particular this would fail (except perhaps by accident) for a
set-returning OpExpr.  I chose to fix it by adding a funcReturnsSet
field to FuncExprState and insisting that ExecInitExpr fill that in
immediately, which it can do easily.

* Minor style and comment improvements; fix a couple small oversights
such as missing outfuncs.c support.

* Update the user documentation (didn't address the CASE issue, though).

            regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachment
Hi,

On 2017-01-18 08:43:24 -0500, Tom Lane wrote:
> I did a review pass over 0001 and 0002.  I think the attached updated
> version is committable

Cool.

> ... except for one thing.  The more I look at it,
> the more disturbed I am by the behavioral change shown in rangefuncs.out
> --- that's the SRF-in-one-arm-of-CASE issue.  (The changes in tsrf.out
> are fine and as per agreement.)  We touched lightly on that point far
> upthread, but didn't really resolve it.  What's bothering me is that
> we're changing, silently, from a reasonably-intuitive behavior to a
> completely-not-intuitive one.  Since we got a bug report for the previous
> less-than-intuitive behavior for such cases, it's inevitable that we'll
> get bug reports for this.  I think it'd be far better to throw error for
> SRF-inside-a-CASE.  If we don't, we certainly need to document this,
> and I'm not very sure how to explain it clearly.

I'm fine with leaving it as is in the patch, but I'm also fine with
changing things to ERROR.  Personally I don't think it matters much, and
we can whack it back and forth as we want later.  Thus I'm inclined to
commit it without erroring out; since presumably we'll take some time
deciding on what exactly we want to prohibit.


> Anyway, I've not done anything about that in the attached.  What I did do:
> 
> * Merge 0001 and 0002.  I appreciate you having separated that for my
> review, but it doesn't make any sense to commit the parts of 0001 that
> you undid in 0002.

Right. I was suggesting upthread that we'd merge them before committing.


> * Obviously, ExecMakeFunctionResultSet can be greatly simplified now
> that it need not deal with hasSetArg cases.

Yea, I've cleaned it up in my 0003; where it would have started to error
out too (without an explicit check), because there's no set evaluating
function anymore besides ExecMakeFunctionResultSet.


> I saw you'd left that
> for later, which is mostly fine, but I did lobotomize it just enough
> to throw an error if it gets a set result from an argument.  Without
> that, we wouldn't really be testing that the planner splits nested
> SRFs correctly.

Ok, that makes sense.


> * This bit in ExecProjectSRF was no good:
> 
> +         else if (IsA(gstate->arg, FuncExprState) &&
> +                  ((FuncExpr *) gstate->arg->expr)->funcretset)
> 
> because FuncExprState is used for more node types than just FuncExpr;
> in particular this would fail (except perhaps by accident) for a
> set-returning OpExpr.

Argh. That should have been FunExprState->func->fn_retset.  Anyway, your
approach works, too.


> * Update the user documentation (didn't address the CASE issue, though).

Cool.


Greetings,

Andres



Andres Freund <andres@anarazel.de> writes:
> On 2017-01-18 08:43:24 -0500, Tom Lane wrote:
>> ... except for one thing.  The more I look at it,
>> the more disturbed I am by the behavioral change shown in rangefuncs.out
>> --- that's the SRF-in-one-arm-of-CASE issue.

> I'm fine with leaving it as is in the patch, but I'm also fine with
> changing things to ERROR.  Personally I don't think it matters much, and
> we can whack it back and forth as we want later.  Thus I'm inclined to
> commit it without erroring out; since presumably we'll take some time
> deciding on what exactly we want to prohibit.

I agree.  If we do decide to throw an error, it would best be done in
parse analysis, and thus would be practically independent of this patch
anyway.

>> * This bit in ExecProjectSRF was no good:
>> +         else if (IsA(gstate->arg, FuncExprState) &&
>> +                  ((FuncExpr *) gstate->arg->expr)->funcretset)

> Argh. That should have been FunExprState->func->fn_retset.

Nope; that was my first thought as well, but fn_retset isn't valid if
init_fcache hasn't been run yet, which it won't have been the first time
through.

So I think we can push this patch now and get on with the downstream
patches.  Do you want to do the honors, or shall I?
        regards, tom lane



Hi,

On 2017-01-18 14:24:12 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2017-01-18 08:43:24 -0500, Tom Lane wrote:
> >> ... except for one thing.  The more I look at it,
> >> the more disturbed I am by the behavioral change shown in rangefuncs.out
> >> --- that's the SRF-in-one-arm-of-CASE issue.
> 
> > I'm fine with leaving it as is in the patch, but I'm also fine with
> > changing things to ERROR.  Personally I don't think it matters much, and
> > we can whack it back and forth as we want later.  Thus I'm inclined to
> > commit it without erroring out; since presumably we'll take some time
> > deciding on what exactly we want to prohibit.
> 
> I agree.  If we do decide to throw an error, it would best be done in
> parse analysis, and thus would be practically independent of this patch
> anyway.

Cool, agreed then.


> >> * This bit in ExecProjectSRF was no good:
> >> +         else if (IsA(gstate->arg, FuncExprState) &&
> >> +                  ((FuncExpr *) gstate->arg->expr)->funcretset)
> 
> > Argh. That should have been FunExprState->func->fn_retset.
> 
> Nope; that was my first thought as well, but fn_retset isn't valid if
> init_fcache hasn't been run yet, which it won't have been the first time
> through.

Righty-O :(


> So I think we can push this patch now and get on with the downstream
> patches.  Do you want to do the honors, or shall I?

Whatever you prefer - either way, I'll go on to rebasing the cleanup
patch afterwards (whose existance should probably be mentioned in the
commit message).


Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> On 2017-01-18 14:24:12 -0500, Tom Lane wrote:
>> So I think we can push this patch now and get on with the downstream
>> patches.  Do you want to do the honors, or shall I?

> Whatever you prefer - either way, I'll go on to rebasing the cleanup
> patch afterwards (whose existance should probably be mentioned in the
> commit message).

OK, I can do it --- I have the revised patch already queued up in git
stash, so it's easy.  Need to write a commit msg though.  Did you have
a draft for that?
        regards, tom lane



Hi,

On January 18, 2017 12:00:12 PM PST, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>Andres Freund <andres@anarazel.de> writes:
>> On 2017-01-18 14:24:12 -0500, Tom Lane wrote:
>>> So I think we can push this patch now and get on with the downstream
>>> patches.  Do you want to do the honors, or shall I?
>
>> Whatever you prefer - either way, I'll go on to rebasing the cleanup
>> patch afterwards (whose existance should probably be mentioned in the
>> commit message).
>
>OK, I can do it --- I have the revised patch already queued up in git
>stash, so it's easy.  Need to write a commit msg though.  Did you have
>a draft for that?

Yea, have something lying around.  Let me push it then when I get back from lunch?

Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.



Andres Freund <andres@anarazel.de> writes:
> Yea, have something lying around.  Let me push it then when I get back from lunch?

Sure, no sweat.
        regards, tom lane



Hi,

On 2017-01-18 15:24:32 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > Yea, have something lying around.  Let me push it then when I get back from lunch?
> 
> Sure, no sweat.

Pushed.  Yay!

There's one sgml comment you'd added:
"Furthermore, nested set-returning functions did not work at all."

I'm not quite sure what you're referring to there - it was previously
allowed to have one set argument to an SRF:

postgres[28758][1]=# SELECT generate_series(1,generate_series(1,5));
┌─────────────────┐
│ generate_series │
├─────────────────┤
│               1 │
│               1 │
│               2 │
│               1 │
│               2 │
│               3 │


Am I misunderstanding what you meant?  I left it in what I committed,
but we probably should clear up the language there.


Working on rebasing the cleanup patch now.  Interested in reviewing
that?  Otherwise I think I'll just push the rebased version of what I'd
posted before, after making another pass through it.


- Andres



Andres Freund <andres@anarazel.de> writes:
> There's one sgml comment you'd added:
> "Furthermore, nested set-returning functions did not work at all."
> I'm not quite sure what you're referring to there - it was previously
> allowed to have one set argument to an SRF:

Ooops ... that was composed too hastily, evidently.  Will fix.

I'll try to write something about the SRF-in-CASE issue too.  Seeing
whether we can document that adequately seems like an important part
of making the decision about whether we need to block it.

> Working on rebasing the cleanup patch now.  Interested in reviewing
> that?  Otherwise I think I'll just push the rebased version of what I'd
> posted before, after making another pass through it.

I have not actually looked at 0003 at all yet.  So yeah, please post
for review after you're done rebasing.
        regards, tom lane



On 2017-01-18 16:56:46 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> I have not actually looked at 0003 at all yet.  So yeah, please post
> for review after you're done rebasing.

Here's a rebased and lightly massaged version. I'm vanishing in a
meeting for a bit, thought it'd be more useful to get it now rather than
later.

(I also noticed the previous patch should have had a catversion bump :(,
will do after the meeting).

- Andres

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachment
Andres Freund <andres@anarazel.de> writes:
> (I also noticed the previous patch should have had a catversion bump :(,
> will do after the meeting).

Uh, why?  It isn't touching any on-disk data structure.
        regards, tom lane



On 2017-01-18 17:34:56 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > (I also noticed the previous patch should have had a catversion bump :(,
> > will do after the meeting).
> 
> Uh, why?  It isn't touching any on-disk data structure.

Forget what I said - I was rushing to a meeting and not thinking
entirely clearly.  Was thinking about the new node types and that we now
(de)serialize plans for parallelism - but that's guaranteed to be the
same version.

Andres



I wrote:
> I'll try to write something about the SRF-in-CASE issue too.  Seeing
> whether we can document that adequately seems like an important part
> of making the decision about whether we need to block it.

Here's what I came up with:
 This behavior also means that set-returning functions will be evaluated even when it might appear that they should be
skippedbecause of a conditional-evaluation construct, such as CASE or COALESCE. For example, consider
 
 SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;
 It might seem that this should produce five repetitions of input rows that have x > 0, and a single repetition of
thosethat do not; but actually it will produce five repetitions of every input row. This is because generate_series()
isrun first, and then the CASE expression is applied to its result rows. The behavior is thus comparable to
 
 SELECT x, CASE WHEN x > 0 THEN g ELSE 0 END   FROM tab, LATERAL generate_series(1,5) AS g;
 It would be exactly the same, except that in this specific example, the planner could choose to put g on the outside
ofthe nestloop join, since g has no actual lateral dependency on tab. That would result in a different output row
order.Set-returning functions in the select list are always evaluated as though they are on the inside of a nestloop
joinwith the rest of the FROM clause, so that the function(s) are run to completion before the next row from the FROM
clauseis considered.
 

So is this too ugly to live, or shall we put up with it?
        regards, tom lane



On 2017-01-18 18:14:26 -0500, Tom Lane wrote:
> I wrote:
> > I'll try to write something about the SRF-in-CASE issue too.  Seeing
> > whether we can document that adequately seems like an important part
> > of making the decision about whether we need to block it.
> 
> Here's what I came up with:
> 
>   This behavior also means that set-returning functions will be evaluated
>   even when it might appear that they should be skipped because of a
>   conditional-evaluation construct, such as CASE or COALESCE. For example,
>   consider
> 
>   SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;
> 
>   It might seem that this should produce five repetitions of input rows
>   that have x > 0, and a single repetition of those that do not; but
>   actually it will produce five repetitions of every input row. This is
>   because generate_series() is run first, and then the CASE expression is
>   applied to its result rows. The behavior is thus comparable to
> 
>   SELECT x, CASE WHEN x > 0 THEN g ELSE 0 END
>     FROM tab, LATERAL generate_series(1,5) AS g;
> 
>   It would be exactly the same, except that in this specific example, the
>   planner could choose to put g on the outside of the nestloop join, since
>   g has no actual lateral dependency on tab. That would result in a
>   different output row order. Set-returning functions in the select list
>   are always evaluated as though they are on the inside of a nestloop join
>   with the rest of the FROM clause, so that the function(s) are run to
>   completion before the next row from the FROM clause is considered.
> 
> So is this too ugly to live, or shall we put up with it?

I'm very tentatively in favor of living with it.

Greetings,

Andres Freund



On Wed, Jan 18, 2017 at 4:14 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I wrote:
> I'll try to write something about the SRF-in-CASE issue too.  Seeing
> whether we can document that adequately seems like an important part
> of making the decision about whether we need to block it.

Here's what I came up with:

  This behavior also means that set-returning functions will be evaluated
  even when it might appear that they should be skipped because of a
  conditional-evaluation construct, such as CASE or COALESCE. For example,
  consider

  SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;

  It might seem that this should produce five repetitions of input rows
  that have x > 0, and a single repetition of those that do not; but
  actually it will produce five repetitions of every input row.

So is this too ugly to live, or shall we put up with it?


​Disallowing such an unlikely, and un-intuitive, corner-case strikes my sensibilities.

​I'd rather fail now and allow for the possibility of future implementation of the "it might seem that..." behavior.​

David J.

On 2017-01-18 16:27:53 -0700, David G. Johnston wrote:
> ​I'd rather fail now and allow for the possibility of future implementation
> of the "it might seem that..." behavior.​

That's very unlikely to happen.



On Wed, Jan 18, 2017 at 6:19 PM, Andres Freund <andres@anarazel.de> wrote:
>>   SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;
>>
>>   It might seem that this should produce five repetitions of input rows
>>   that have x > 0, and a single repetition of those that do not; but
>>   actually it will produce five repetitions of every input row. This is
>>   because generate_series() is run first, and then the CASE expression is
>>   applied to its result rows. The behavior is thus comparable to
>>
>>   SELECT x, CASE WHEN x > 0 THEN g ELSE 0 END
>>     FROM tab, LATERAL generate_series(1,5) AS g;
>>
>>   It would be exactly the same, except that in this specific example, the
>>   planner could choose to put g on the outside of the nestloop join, since
>>   g has no actual lateral dependency on tab. That would result in a
>>   different output row order. Set-returning functions in the select list
>>   are always evaluated as though they are on the inside of a nestloop join
>>   with the rest of the FROM clause, so that the function(s) are run to
>>   completion before the next row from the FROM clause is considered.
>>
>> So is this too ugly to live, or shall we put up with it?
>
> I'm very tentatively in favor of living with it.

So, one of the big reasons I use CASE is to avoid evaluating
expressions in cases where they might throw an ERROR.  Like, you know:

CASE WHEN d != 0 THEN n / d ELSE NULL END

I guess it's not the end of the world if that only works for
non-set-returning functions, but it's something to think about.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




On January 18, 2017 3:59:00 PM PST, Robert Haas <robertmhaas@gmail.com> wrote:
>On Wed, Jan 18, 2017 at 6:19 PM, Andres Freund <andres@anarazel.de>
>wrote:
>>>   SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END
>FROM tab;
>>>
>>>   It might seem that this should produce five repetitions of input
>rows
>>>   that have x > 0, and a single repetition of those that do not; but
>>>   actually it will produce five repetitions of every input row. This
>is
>>>   because generate_series() is run first, and then the CASE
>expression is
>>>   applied to its result rows. The behavior is thus comparable to
>>>
>>>   SELECT x, CASE WHEN x > 0 THEN g ELSE 0 END
>>>     FROM tab, LATERAL generate_series(1,5) AS g;
>>>
>>>   It would be exactly the same, except that in this specific
>example, the
>>>   planner could choose to put g on the outside of the nestloop join,
>since
>>>   g has no actual lateral dependency on tab. That would result in a
>>>   different output row order. Set-returning functions in the select
>list
>>>   are always evaluated as though they are on the inside of a
>nestloop join
>>>   with the rest of the FROM clause, so that the function(s) are run
>to
>>>   completion before the next row from the FROM clause is considered.
>>>
>>> So is this too ugly to live, or shall we put up with it?
>>
>> I'm very tentatively in favor of living with it.
>
>So, one of the big reasons I use CASE is to avoid evaluating
>expressions in cases where they might throw an ERROR.  Like, you know:
>
>CASE WHEN d != 0 THEN n / d ELSE NULL END
>
>I guess it's not the end of the world if that only works for
>non-set-returning functions, but it's something to think about.

That's already not reliable in a bunch of cases, particularly evaluation during planning...  Not saying that's good,
butit is. 

Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.



On Wed, Jan 18, 2017 at 7:00 PM, Andres Freund <andres@anarazel.de> wrote:
>>So, one of the big reasons I use CASE is to avoid evaluating
>>expressions in cases where they might throw an ERROR.  Like, you know:
>>
>>CASE WHEN d != 0 THEN n / d ELSE NULL END
>>
>>I guess it's not the end of the world if that only works for
>>non-set-returning functions, but it's something to think about.
>
> That's already not reliable in a bunch of cases, particularly evaluation during planning...  Not saying that's good,
butit is.
 

Whee!

:-)

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Robert Haas <robertmhaas@gmail.com> writes:
> So, one of the big reasons I use CASE is to avoid evaluating
> expressions in cases where they might throw an ERROR.  Like, you know:
> CASE WHEN d != 0 THEN n / d ELSE NULL END
> I guess it's not the end of the world if that only works for
> non-set-returning functions, but it's something to think about.

Well, refusing CASE-containing-SRF at all isn't going to make your
life any better in that regard :-(

It's possibly worth noting that this is also true for aggregates and
window functions: wrapping those in a CASE doesn't stop them from being
evaluated, either.  People seem to be generally used to that, although
I think I've seen one or two complaints about it from folks who seemed
unclear on the concept of aggregates.

In the end I think this is mostly about backwards compatibility:
are we sufficiently worried about that that we'd rather throw an
error than have a silent change of behavior?  TBH I'm not sure.
We've certainly got two other silent changes of behavior in this
same patch.  The argument for treating this one differently,
I think, is that it's changing from a less surprising behavior
to a more surprising one whereas the other changes are the reverse,
or at worst neutral.
        regards, tom lane



Andres Freund <andres@anarazel.de> writes:
> On 2017-01-18 16:56:46 -0500, Tom Lane wrote:
>> Andres Freund <andres@anarazel.de> writes:
>> I have not actually looked at 0003 at all yet.  So yeah, please post
>> for review after you're done rebasing.

> Here's a rebased and lightly massaged version.

I've read through this and made some minor improvements, mostly additional
comment cleanup.  One thing I wanted to ask about:

@@ -4303,7 +4303,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,

     /*
      * Forget it if the function is not SQL-language or has other showstopper
-     * properties.  (The nargs check is just paranoia.)
+     * properties.  (The nargs and retset checks are just paranoia.)
      */
     if (funcform->prolang != SQLlanguageId ||
         funcform->prosecdef ||

I thought this change was simply wrong, and removed it; AFAIK it's
perfectly possible to get here for set-returning functions, since
the planner does expression simplification long before it worries
about splitting out SRFs.  Did you have a reason to think differently?

Other than that possible point, I think the attached is committable.

            regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachment
On 2017-01-19 13:06:20 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2017-01-18 16:56:46 -0500, Tom Lane wrote:
> >> Andres Freund <andres@anarazel.de> writes:
> >> I have not actually looked at 0003 at all yet.  So yeah, please post
> >> for review after you're done rebasing.
>
> > Here's a rebased and lightly massaged version.
>
> I've read through this and made some minor improvements, mostly additional
> comment cleanup.

Thanks!


> One thing I wanted to ask about:
>
> @@ -4303,7 +4303,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
>
>      /*
>       * Forget it if the function is not SQL-language or has other showstopper
> -     * properties.  (The nargs check is just paranoia.)
> +     * properties.  (The nargs and retset checks are just paranoia.)
>       */
>      if (funcform->prolang != SQLlanguageId ||
>          funcform->prosecdef ||
>
> I thought this change was simply wrong, and removed it;

Hm. I made that change a while ago.  It might have been a holdover from
the old approach, where it'd indeed have been impossible to see any
tSRFs here.  Or it might have been because we check
querytree->hasTargetSRFs below (which should prevent inlining a function
that actually returns multiple rows).  I agree it's better to leave the
check there. Maybe we ought to remove the paranoia bit about retset
though - it's not paranoia if it has an effect.


> Other than that possible point, I think the attached is committable.

Will do so in a bit, after a s/and retset checks are/check is/. And then
fix that big-endian ordering issue.

- Andres



Andres Freund <andres@anarazel.de> writes:
> Maybe we ought to remove the paranoia bit about retset
> though - it's not paranoia if it has an effect.

Exactly, and I already did that in my version.
        regards, tom lane